pydoc – Documentação de módulos Python

Em mais um post sobre documentação, vamos conhecer hoje o pydoc [1]. O pydoc é um módulo que gera documentação (em formato amigável) sobre módulos Python, a partir dos docstrings que estão presentes nestes. A documentação pode ser apresentada em mais de uma forma. Uma delas, é o formato manpage [2]. Para ver a documentação de um módulo em formato manpage, podemos invocar o pydoc através de um shell do sistema operacional:

$ pydoc timeit

A documentação pode ser vista na tela abaixo:

Uma vantagem óbvia disso é não precisar abrir um shell python para apenas tirar uma dúvida sobre algum método. Além da visualização manpage-style, o pydoc também gera documentação em formato HTML.

$ pydoc -w timeit
wrote timeit.html

O comando acima gera um arquivo com a extensão html com o nome do módulo em questão, para ser visualizado em um browser.

Além de mostrar a documentação em formato manpage e de gerar arquivos html com a documentação, também é possível “levantar” um servidor web que irá servir as páginas html com a documentação dos módulos disponíveis no sistema. Para isso, basta chamar o pydoc com a opção -p, passando o número da porta onde o servidor irá escutar como argumento.
$ pydoc -p 8080
pydoc server ready at http://localhost:8080/

Acessando no browser o endereço localhost:8080, é possível navegar nas páginas de documentação dos módulos.

Por fim, existe outra opção que abre uma interface gráfica onde o usuário poderá procurar pelo módulo sobre o qual deseja ver a documentação e abrir o html correspondente no browser.

Na figura acima, ao dar duplo-clique sobre um dos itens retornados pela busca, é aberto no browser o documento html correspondente ao módulo escolhido.

É claro que, além dessas formas de visualizar a documentação dos módulos, também podemos usar o tradicional builtin help() para ver a documentação dentro do shell Python.

[1] http://docs.python.org/library/pydoc.html

[2] https://en.wikipedia.org/wiki/Man_page

Anúncios

Propriedades em Python

Em algumas situações, ao criamos uma classe, não desejamos que os atributos que compõem um objeto dessa classe sejam alterados sem prévia validação. Para isso, costumamos definir os atributos como privados e então escrever métodos get_NOME_ATRIBUTO() e set_NOME_ATRIBUTO(), onde são colocados os testes antes de modificar o objeto. Podemos usar properties em Python para tornar nosso código independente de montes de métodos get e set. Antes de vermos as properties, vamos dar uma passada rápida sobre atributos privados.

Atributos Privados em Python

Antes de mais nada, Python não possui um mecanismo explícito para que definamos um atributo como privado, ao contrário de outras linguagens como Java. O que existe é uma espécie de truque, onde o nome dos atributos privados é precedido por dois underscores (“__”). Veja o exemplo abaixo:

>>> class Ponto:
...     def __init__(self, x, y):
...         self.__x = x
...         self.__y = y
...
>>> p = Ponto(2,3)
>>> print p.__x
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: Ponto instance has no attribute '__x'

No código acima, criamos uma classe com dois atributos (__x e __y). Veja que ao tentar acessar o atributo __x de fora da classe, obtemos uma mensagem de erro, dizendo que tal atributo não existe. Isso mesmo, para fora da classe ele não é visível (ao menos não como __x). O que acontece, na realidade, é que o interpretador Python altera o nome dos atributos que se iniciam com “__”, colocando “_NomeClasse” na frente do nome do atributo. Por exemplo, o código acima ao ser interpretado pelo Python, tem  suas variáveis __x e __y transformadas em _Ponto__x e _Ponto__y. Veja:

>>> dir(p)
['_Ponto__x', '_Ponto__y', '__doc__', '__init__', '__module__']

Se tentarmos acessar os atributos através desses nomes, conseguimos acessar seus valores normalmente:

>>> print p._Ponto__x
2
>>> print p._Ponto__y
3

Ou seja, os atributos não são realmente privados. O que acontece é que o interpretador modifica os nomes deles.

Properties

Python oferece um mecanismo builtin para construção de propriedades para uma classe. Propriedades são elementos acessados externamente como se fossem atributos, mas que internamente (à classe), são manipulados por funções. Vamos a um exemplo:

class Ponto:

    def __init__(self, x, y):
        self.__x = x
        self.__y = y

    def get_x(self):
        return self.__x
    def get_y(self):
        return self.__y

    def set_x(self, x):
        if x > 0:
            self.__x = x

    def set_y(self, y):
        if y > 0:
            self.__y = y

p = Ponto(2, 3)
print p.get_x()
p.set_x(10)

Cada acesso à variável __x deve ser feito através das funções get e set definidas para essa variável. Mas, também podemos fazer isso de modo que o acesso à variável (de fora da classe) seja mais “elegante”. Vamos montar uma property chamada x, de forma que qualquer acesso a p.x irá chamar as funções atribuídas à variável.

class Ponto(object):

    def __init__(self, x, y):
        self.__x = x
        self.__y = y

    def get_x(self):
        return self.__x

    def get_y(self):
        return self.__y

    def set_x(self, x):
        if x > 0:
            self.__x = x

    def set_y(self, y):
        if y > 0:
            self.__y = y

    x = property(fget=get_x, fset=set_x)

p = Ponto(2, 3)
p.x = -1000
print p.x

O resultado da execução do código acima irá mostrar na tela o número 2, que foi o valor atribuído a x na construção do objeto. Ao executar a penúltima linha (p.x = -1000), o método set_x() será chamado automaticamente, pois foi configurado como a função fset da propriedade x. Como o método set_x() não permite a alteração da variável interna __x caso o novo valor seja menor ou igual a zero, então esse valor não é alterado. Ao executar a última linha (print p.x), é chamado o método get_x(), configurado como o método fget na construção da propriedade. Ele irá retornar o valor de __x, que permanece ainda sendo o valor 2.

É importante observar a linha (x = property(get_x, set_x)). É ela que cria essa propriedade x que pode ser acessada externamente. Como parâmetros, passamos as duas funções que vão ser chamadas ao ser feita alguma operação sobre essa propriedade. Assim, de fora de nossa classe, os acessos são feitos à property x, quando na verdade estamos acessando internamente o atributo __x. Ou seja, para quem está utilizando nossa classe, instanciando objetos dela, x é como se fosse um atributo público, visível de fora da classe. Mas, isso não impede que sejam feitos acessos externos à classe às variáveis _Ponto__x e _Ponto__y sem passar por método algum.

A assinatura da função que cria a propriedade é:

property([fget[, fset[, fdel[, doc]]]])

Mais informações em: http://docs.python.org/library/functions.html#property

subprocess – Executando programas externos

Quando estava aprendendo a programar em linguagem C, no início da graduação, descobri a famigerada função system() que servia para executar um programa/comando em um shell a partir de um programa em linguagem C. A possibilidade de executar programas externos logo me chamou a atenção, mas a fissura durou pouco tempo. Logo descobri suas limitações. Percebi que quando fazia:

x = system("ls -l");

A única coisa que retornava para a variável x era o status de execução do comando passado como argumento. Ou seja, meu programa pouco poderia interagir diretamente com a saída que o “ls -l” (ou qualquer comando passado) gerava, exceto saber se a execução ocorreu com sucesso ou não. Se eu quisesse usar algum valor que o comando jogasse na tela, simplesmente não poderia. Claro que sempre dava pra fazer gambiarra, como redirecionar a saída do comando para um arquivo (via redirecionamento do shell) e depois ler tal arquivo. Como:

x = system("ls -l > saida.txt");
if (fp = fopen("saida.txt", "r")) {
. . .

😛

Depois descobri que existiam outras formas de fazer isso. Mas isso não vem ao caso agora. O que quero mostrar nesse post é que existe um módulo Python que permite que criemos processos, executando programas externos ao nosso código e permitindo que analisemos a saída que tal programa geraria na tela se executado em um shell comum. Esse módulo se chama subprocess.
O subprocess oferece três funções principais para execução de programas:
  • call()
  • check_call()
  • check_output()
Considere que precisamos, em nosso programa, obter informações sobre as partições do disco rídigo. Poderíamos invocar a execução do programa df (man df), que mostra informações sobre o espaço em disco das partições e verificar a saída deste.
Podemos, inicialmente, usar a função call():
>>> import subprocess
>>> r = subprocess.call(["df", "-h"])
Filesystem Size Used Avail Use% Mounted on
/dev/sda7 189G 5.4G 174G 3% /
udev 1.9G 4.0K 1.9G 1% /dev
tmpfs 764M 1.1M 763M 1% /run
none 5.0M 0 5.0M 0% /run/lock
none 1.9G 500K 1.9G 1% /run/shm
/dev/sda6 221G 6.2G 203G 3% /home
>>> print r
0
>>>
Observe que a função call() recebeu como entrada uma lista, contendo o nome do programa a ser executado (df), seguido pelos argumentos para esse programa (-h (–human-readable)). Veja que a chamada à função fez com que a saída do programa df fosse impressa na saída padrão. O valor que a função call() retorna nada mais é do que o status de execução do programa executado (no caso, 0, ou, sucesso).
Certo, mas e se precisarmos usar os valores lidos para realizar alguma operação? A função call() não seria suficiente, pois não está permitindo que acessemos o resultado mostrado na tela.
Para resolver isso, podemos usar a função check_output():
>>> r = subprocess.check_output(["df", "-h"])
>>> print r
Filesystem Size Used Avail Use% Mounted on
/dev/sda7 189G 5.4G 174G 3% /
udev 1.9G 4.0K 1.9G 1% /dev
tmpfs 764M 1.1M 763M 1% /run
none 5.0M 0 5.0M 0% /run/lock
none 1.9G 500K 1.9G 1% /run/shm
/dev/sda6 221G 6.2G 203G 3% /home

Veja que agora temos a saída do programa sendo retornada pela função check_output(), o que permite que manipulemos a saída como quisermos:

import subprocess
r = subprocess.check_output(["df", "-h"])
for linha in r.split('\n'):
    if 'sda6' in linha:
        print linha.split(' ')[-1]

O código acima, por exemplo mostra somente o ponto de montagem da partição sda6. Assim como realizamos essa operação, poderíamos pegar valores de espaço em disco, ou outras coisas que fossem necessárias, tratando a saída do programa df, chamado através do módulo subprocess.

Mais informações em: http://docs.python.org/library/subprocess.html

Por que __name__ == “__main__” ?

Já viu algum programa escrito em Python, que lá pelo final tem um trecho de código parecido com o código abaixo?

if __name__ == "__main__":
    # faça algo

Quem não conhece, deve estar se perguntando: que condição é essa? O que é __name__? O que é “__main__”?

Para entender isso, nada melhor que a prática. Faça o seguinte:

  • Usando seu editor de textos favorito, crie um arquivo .py (por exemplo: teste.py);
  • Dentro desse arquivo, coloque o seguinte código:

print __name__

OK, criamos um programa/módulo python chamado teste. Isso quer dizer que podemos executá-lo pela linha de comando, ou importá-lo em um shell python. Agora vamos executá-lo como um programa:


user@host:~/ $ python teste.py

__main__

Repare na saída que o programa gerou: __main__. Esse é o nome interno que todo programa/módulo Python recebe quando executado como um programa pela linha de comando.

Agora, abra um shell Python no mesmo diretório onde o arquivo teste.py foi gravado e importe tal módulo:

Python 2.7.2+ (default, Oct 4 2011, 20:06:09)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import teste
teste
>>>

Repare que agora, a variável __name__ foi impressa com um valor diferente: teste, que é o nome que demos ao nosso programa. Assim, o teste __name__ == “__main__” está verificando nada mais do que se o código do módulo está sendo executado como um programa, tendo sido chamado pela linha de comando, ou sendo importado como um módulo. Mas, pra que serve isso?

Vamos a outro exemplo. Vamos implementar um módulo com algumas funções que achamos úteis. Vamos chamar esse módulo como utilidades.py.

import sys

def erro(msg):
    print "Erro:", msg
    sys.exit(1)

def inc(x):
    return x + 1

def dec(x):
    return x - 1

def quadrado(x):
    return x**2

Mas, enquanto vamos implementando, queremos fazer alguns testes para saber se o código funciona como esperamos, então complementamos o código acima com uns testes ao final:

import sys
def erro(msg):
    print "Erro:", msg
    sys.exit(1)

def inc(x):
    return x + 1

def dec(x):
    return x - 1

def quadrado(x):
    return x**2

print inc(10) # deve mostrar 11
print dec(10) # deve mostrar 9
print quadrado(5) # deve mostrar 25

Perfeito, se executarmos o código acima pela linha de comando, teremos o resultado que esperamos:

user@host:~/ $ python utilidades.py
11
9
25

Até aí, tudo certo. Mas, e se precisarmos de uma dessas funções e importarmos esse módulo (utilidades) em algum outro programa ou até mesmo em um shell Python? Vamos ver:

Python 2.7.2+ (default, Oct 4 2011, 20:06:09)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import utilidades
11
9
25
>>>

Opa! “Mas eu queria apenas usar as funções que o módulo utilidades disponibiliza, não ver esses números na tela!”. Pois é. O problema é que as três linhas de código que finalizam o módulo estão sendo executadas de forma incondicional, não importando se o programa estiver sendo executado pela linha de comando ou sendo importado em outro programa. Podemos resolver esse problema adicionando o teste __name__ == “__main__”:

import sys

def erro(msg):
    print "Erro:", msg
    sys.exit(1)

def inc(x):
    return x + 1

def dec(x):
    return x - 1

def quadrado(x):
    return x**2

if __name__ == "__main__":
    print inc(10)
    print dec(10)
    print quadrado(5)
Agora, o comportamento do programa será diferente quando executado pela linha de comando (quando o __name__ for igual a string “__main__”), do que quando for importado como módulo.

Acesso fácil à documentação com ipython

Para quem não sabe, quando desejamos informações de ajuda sobre algum módulo/método/builtin, podemos invocar o builtin help(). Por exemplo, quero saber detalhes sobre a função len():

>>> help(len)
Help on built-in function len in module __builtin__:
len(...)
 len(object) -> integer
 Return the number of items of a sequence or mapping.
(END)

Isso irá ler o atributo __doc__ do objeto em questão. Mas, em minha opinião de preguiçoso, é um pouco chato sempre chamar a função help(), passando como argumento o objeto do qual desejamos mais informações. Se você concorda comigo, saiba que o ipython [1] fornece um “atalho” bem simples para acessarmos a documentação de determinado objeto. Basta adicionar um ponto de interrogração ao final do nome do objeto, que o ipython mostra o texto de ajuda. Veja a figura abaixo:

iPython mostrando informações sobre método.

Além da interrogação simples (?), podemos utilizar também a interrogação dupla (??) para obter informações adicionais sobre o método/módulo, incluindo o seu código-fonte, quando disponível. Veja abaixo:

iPython mostrando informações extra

É claro que as vantagens do ipython vão muito além desse recurso. Mas, as outras ficam para outro post.

[1] http://ipython.org/

Testando código – doctest

Em um post antigo, vimos o que são docstrings e para que servem. No post de hoje, veremos como utilizá-las para especificar testes sobre nossas funções.

Considere que estamos escrevendo um programa, e precisamos escrever uma função que calcule o dobro de um determinado número. Então, escrevemos o código:

def dobro(x):
    result = x * 2
    return result

Agora, para saber se está funcionando corretamente, vamos testar chamando a função dobro, passando como argumento alguns valores para os quais nós conhecemos o valor de retorno que a função deveria retornar:

>>> dobro(1)
2
>>> dobro(2)
4
>>> dobro(3)
6
>>> dobro(4)
8
>>> dobro(5)
10

Verificando visualmente os resultados, nos parece que a função está funcionando corretamente (e está, de fato). Daí, vem um colega e diz que não é necessário criarmos uma variável result, podendo retornar diretamente o retorno da expressão x * 2. Daí vamos editar nosso código para que se pareça com o código abaixo:

def dobro(x):
    return x * 2

Agora, teremos que rodar novamente os 5 testes acima e verificar, linha por linha, se o resultado está correto. Isso é chato, e também, em casos mais complexos, muito propenso a erros. Para não precisar rodar manualmente os testes e verificar visualmente se o resultado está correto, vamos utilizar o módulo doctest [1]. Com esse módulo, basta que colemos os testes (realizados no terminal interativo) dentro da docstring da função correspondente. Por exemplo, para aplicar doctests na nossa função dobro, vamos modificar o código para que se pareça com o código abaixo:

def dobro(x):
	"""
	Funcao que retorna o dobro do valor passado como argumento.
	>>> dobro(1)
	2
	>>> dobro(2)
	4
	>>> dobro(3)
	6
	>>> dobro(4)
	8
    >>> dobro(5)
    10
	"""
	return x * 2

Agora, vou executar os testes, chamando o módulo doctest pela linha de comando de um shell Linux (poderia ser por um terminal Windows/Mac):

user@host:~/ $ python -m doctest -v t.py
Trying:
 dobro(1)
Expecting:
 2
ok
Trying:
 dobro(2)
Expecting:
 4
ok
Trying:
 dobro(3)
Expecting:
 6
ok
Trying:
 dobro(4)
Expecting:
 8
ok
Trying:
 dobro(5)
Expecting:
 10
ok
1 items had no tests:
 t
1 items passed all tests:
 5 tests in t.dobro
5 tests in 2 items.
5 passed and 0 failed.
Test passed.

Ao chamar o módulo doctest pela linha de comando, passando como entrada o arquivo que contém a função dobro() (t.py, no caso), esse módulo vai varrer o código-fonte atual em busca de funções que contenham docstrings cujo conteúdo seja similar ao que aparece em uma tela de terminal interativo do Python. Ao se deparar com tal conteúdo (como por exemplo: >>> dobro(1)), o doctest vai executar tal código e ver se o resultado é igual ao que aparece na linha seguinte na docstring. Assim, se tivermos feito alguma alteração que quebrou o funcionamento da função, ao rodar o doctest, esse erro será acusado. Vamos ver isso na prática. Vou alterar a expressão de retorno da função dobro para x * 3:

def dobro(x):
    """
    Funcao que retorna o dobro do valor passado como argumento.
    >>> dobro(1)
    2
    >>> dobro(2)
    4
    >>> dobro(3)
    6
    >>> dobro(4)
    8
    >>> dobro(5)
    10
    """
    return x * 3

Agora, vou rodar o código acima e vamos ver o resultado.


user@host:~/ $ python -m doctest -v t.py
 Trying:
 dobro(1)
 Expecting:
 2
 **********************************************************************
 File "t.py", line 4, in t.dobro
 Failed example:
 dobro(1)
 Expected:
 2
 Got:
 3
 Trying:
 dobro(2)
 Expecting:
 4
 **********************************************************************
 File "t.py", line 6, in t.dobro
 Failed example:
 dobro(2)
 Expected:
 4
 Got:
 6
 Trying:
 dobro(3)
 Expecting:
 6
 **********************************************************************
 File "t.py", line 8, in t.dobro
 Failed example:
 dobro(3)
 Expected:
 6
 Got:
 9
 Trying:
 dobro(4)
 Expecting:
 8
 **********************************************************************
 File "t.py", line 10, in t.dobro
 Failed example:
 dobro(4)
 Expected:
 8
 Got:
 12
 Trying:
 dobro(5)
 Expecting:
 10
 **********************************************************************
 File "t.py", line 12, in t.dobro
 Failed example:
 dobro(5)
 Expected:
 10
 Got:
 15
 1 items had no tests:
 t
 **********************************************************************
 1 items had failures:
 5 of 5 in t.dobro
 5 tests in 2 items.
 0 passed and 5 failed.
 ***Test Failed*** 5 failures.
 

Ao final, podemos ver um mini-relatório da execução.

Assim, sempre que for preciso alterar minha função dobro(), não precisarei rodar e conferir os resultados manual e visualmente. Basta manter a doctring dentro da função e executar o o módulo doctest passando como entrada o arquivo que contém minha função, que as verificações serão realizadas por este.

[1] http://docs.python.org/library/doctest.html

O módulo getpass

Embora não seja um grande problema para quem desenvolve aplicativos web ou que utilizem algum outro tipo de GUI (graphical user interface), quando escrevemos um aplicativo que irá operar somente em modo texto, ficamos na dúvida de como fazer para ler do teclado um campo como a senha do usuário. Se utilizarmos uma função como raw_input(), cada caractere que o usuário digitar irá aparecer, deixando assim a senha visível no terminal. Por exemplo:

username = raw_input('username: ')
password = raw_input('password: ')
print 'OK.'

Ao executar um programa que tenha as linhas de código acima para ler nome do usuário e senha do teclado, irá acontecer o seguinte:

username: teste
password: minhasenha123
OK.

Como podemos ver, fica muito ruim, afinal a senha do usuário fica exposta. A solução para que o usuário possa digitar sua senha sem que ela apareça (seja ecoada) na tela pode ser encontrada no módulo getpass [1]. Esse módulo fornece duas funções:

  • getpass(): apresenta ao usuário um prompt solicitando a sua senha e não ecoa na tela os caracteres digitados por ele.
  • getuser(): retorna o username do usuário atual no sistema (para Linux e Windows).

O uso de ambas as funções é bem simples. A função getpass() pode receber um argumento que é um texto que será apresentado ao usuário como prompt. Teste o seguinte código:

import getpass
username = getpass.getuser()
password = getpass.getpass('Digite sua senha: ')

Como você verá, será apresentado ao usuário a mensagem “Digite sua senha: “, com o cursor ao lado esperando pela entrada do usuário. Ao digitar a senha, o cursor permanece parado, não dando indicativo visual algum sobre a senha digitada.

É isso. Se precisar escrever um programa que leia do teclado a senha do usuário e não quer que ela seja ecoada, use o módulo getpass.

 

[1] http://docs.python.org/library/getpass.html