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

Módulo timeit

Como já escrevi em um post anterior, podemos utilizar o módulo timeit (Time it!) para medir o tempo de execução de nossos programas em Python. Porém, às vezes queremos apenas verificar qual trecho de código obtém menor tempo de execução, para escolher qual abordagem seguir. Por exemplo, quero saber o que acarreta em um menor tempo de execução: fazer um for com a função range() ou com a função xrange()?

Nada melhor do que testar na prática para descobrir qual nos dá o menor tempo de resposta. Para isso, podemos usar a interface de linha de comando que o módulo timeit fornece, quando executado como módulo.

python -m timeit

Assim, podemos testar:

python -m timeit "for i in range(1, 10000): pass"
1000 loops, best of 3: 303 usec per loop
python -m timeit "for i in xrange(1, 10000): pass"
1000 loops, best of 3: 206 usec per loop

Como podemos ver, o uso da função xrange() nos deu um tempo de execução de 97 milisegundos a menos do que utilizando a função range(), considerando o melhor dos casos para ambos. No exemplo acima, o timeit executou 3 baterias de testes compostas por 1000 execuções do código passado como argumento.

Em muitos casos iremos precisar testar um código que possui mais de uma linha. Isso pode ser feito passando cada uma das linhas como um argumento separado para o programa. Por exemplo:

x = 0
for i in range(0, 1000):
    x = i * 2

O código acima poderia ser testado pela linha de comando da seguinte forma:

python -m timeit "x=0" "for i in range(1, 10000):" "    x = i * 2"

Ou seja, cada linha é uma string separada e a indentação é feita linha por linha.

Por que python -m?

Se você está se perguntando o que significa python -m timeit, eis a resposta: ao chamar o interpretador python com a opção -m seguida pelo nome de um módulo existente no ambiente, o interpretador irá buscar o arquivo .py que representa tal módulo e executá-lo como se fosse um programa. O módulo timeit, em meu sistema, fica localizado em /usr/lib/python2.7/timeit.py (como eu sei isso? leia aqui). O que é feito quando executamos python -m timeit é o mesmo que:

python /usr/lib/python2.7/timeit.py "for i in xrange(1, 10000): pass"

Vá em frente e teste.

 

O timeit pode ser usado também dentro de programs Python. Veja mais exemplos: http://docs.python.org/library/timeit.html#examples

Problemas importando módulos em Python

Sendo professor de disciplinas de introdução à programação com Python, percebi que muitos alunos cometem um erro muito comum e bastante difícil de ser descoberto quando estamos começando.

De vez em quando, apresento módulos Python aos alunos e peço que eles façam um pequeno exemplo utilizando aquele módulo. Ao dar nome ao arquivo-fonte que está digitando, o aluno acaba nomeando o seu arquivo com o mesmo nome do módulo a ser usado.

Por exemplo, peço aos alunos para escrever um programinha simples para conhecer melhor o módulo math. Então, o aluno cria um arquivo chamado math.py, onde digita seu código. Entre as linhas de código inseridas no arquivo, estão:

import math
print math.pi

Ao tentar executar o programa, o usuário receberá uma mensagem dizendo que não existe um atributo pi no módulo math. Daí, o aluno vai lá e pesquisa na documentação do módulo math e vê que o atributo pi de fato existe naquele módulo. O que há de errado?

Simples. Quando executa o programa recém escrito (math.py), a primeira linha faz import de um módulo chamado math(.py). Como o diretório atual faz parte dos caminhos onde o interpretador python busca os módulos que o programador importa, o interpretador acaba, inadvertidamente, importando o arquivo que o usuário criou, ao invés de importar o módulo math(.py) original. Para resolver esse problema, renomeie seu arquivo.

🙂

Quer descobrir em quais diretórios Python busca os módulos importados pelos programas? Execute o código abaixo:

import sys
print sys.path

Onde está localizado um módulo Python no disco?

Às vezes, quando importamos um módulo em Python, surge a curiosidade para ler o código-fonte desse módulo, para descobrir o que aquele código faz realmente. Existe uma forma bem simples de descobrir a localização de um módulo no disco. Após importar um módulo, basta verificar o atributo __file__ do módulo.

>>> import os
>>> print os.__file__
/usr/lib/python2.7/os.pyc

Esse arquivo, cujo caminho é impresso na tela é, na realidade, o arquivo binário contendo o bytecode compilado. Porém, se retirar o c do final do nome do arquivo, teremos o arquivo-fonte (.py). No exemplo acima, podemos ver o código-fonte do módulo em:

/usr/lib/python2.7/os.py

Outro exemplo, se quisermos ver o código-fonte do módulo timeit, fazemos o seguinte:

>>> import timeit
>>> print timeit.__file__
/usr/lib/python2.7/timeit.pyc

O arquivo-fonte então está em:

/usr/lib/python2.7/timeit.py

Também poderíamos procurar pelo módulo dentro da lista sys.path, que contém os caminhos onde o interpretador busca pelos módulos a serem importados.

map(), reduce(), filter() e lambda

map()

map() é uma função builtin de Python, isto é, uma função que é implementada diretamente no interpretador Python, podendo ser utilizada sem a importação de um módulo específico. Essa função, em Python, serve para aplicarmos uma função a cada elemento de uma lista, retornando uma nova lista contendo os elementos resultantes da aplicação da função. Considere o exemplo abaixo:

>>> import math
>>> lista1 = [1, 4, 9, 16, 25]
>>> lista2 = map(math.sqrt, lista1)
>>> print lista2
[1.0, 2.0, 3.0, 4.0, 5.0]

Ao chamar a função map(math.sqrt, lista1), estamos solicitando ao interpretador para que execute a função math.sqrt (raiz quadrada – square root) usando como entrada cada um dos elementos de lista1, e inserindo o resultado na lista retornada como resultado da função map().

É uma forma bem interessante e expressiva de denotar a aplicação de uma função a cada elemento de uma lista (ou sequência). Mas, podemos facilmente substituir uma chamada a map() por list comprehensions. O código recém listado poderia ser substituído por:

>>> lista2 = [math.sqrt(x) for x in lista1]
>>> print lista2
[1.0, 2.0, 3.0, 4.0, 5.0]

O código acima produz o mesmo resultado que map(), pois, para cada elemento de lista1, executa a função math.sqrt e inclui o resultado dessa execução na lista de retorno.

O fato de a função map() ser tão facilmente substituída pelo uso de comprehensions, já criou até mesmo algumas discussões sobre manter ou não map() entre as funções builtin do Python 3000 [1].

reduce()

reduce() é outra função builtin do Python (deixou de ser builtin e passou a estar disponível no módulo functools a partir da versão 3000). Sua utilidade está na aplicação de uma função a todos os valores do conjunto, de forma a agregá-los todos em um único valor. Por exemplo, para aplicar a operação de soma a todos os elementos de uma sequência, de forma que o resultado final seja a soma de todos esses elementos, poderíamos fazer o seguinte:

>>> import operator #necessário para obter a função de soma
>>> valores = [1, 2, 3, 4, 5]
>>> soma = reduce(operator.add, valores)
>>> print soma
15

É claro que, para realizar a soma de todos os elementos de uma sequência, é muito mais claro utilizarmos a função sum():

>>> print sum(valores)
15

Como falei anteriormente, reduce() foi retirada do conjunto de builtins de Python, em parte devido à obscuridade que pode acabar gerando [1].

filter()

Como o próprio nome já deixa claro, filter() filtra os elementos de uma sequência. O processo de filtragem é definido a partir de uma função que o programador passa como primeiro argumento da função. Assim, filter() só “deixa passar” para a sequência resultante aqueles elementos para os quais a chamada da função que o usuário passou retornar True. Vejamos um exemplo:

>>> def maior_que_zero(x):
...     return x > 0
...
>>> valores = [10, 4, -1, 3, 5, -9, -11]
>>> print filter(maior_que_zero, valores)
[10, 4, 3, 5]

No exemplo acima, filter() chamou a função maior_que_zero para cada um dos elementos contidos em valores. Se a função retornar True, o elemento é inserido na lista de resultado. Caso contrário, não o é. Ou seja, é feita a filtragem e só passam aqueles elementos que são maiores que zero.

Assim, como no exemplo da builtin map(), aqui também podemos escrever com facilidade uma comprehension com a mesma funcionalidade:

>>> print [x for x in valores if x > 0]
[10, 4, 3, 5]

Devido a essa fácil substituição, filter() também já esteve na mira para ser retirada do conjunto de builtins, embora tenha acabado permanecendo.

lambda

No exemplo da função filter(), tivemos que definir uma nova função (chamada maior_que_zero) para usar somente dentro da função filter(), sendo chamada uma vez para cada elemento. Ao invés de definir uma nova função dessa forma, poderíamos definir uma função válida somente enquanto durar a execução do filter. Não é necessário nem dar um nome a tal função, sendo portanto chamada de função anônima ou função lambda. Considere o exemplo abaixo:

>>> valores = [10, 4, -1, 3, 5, -9, -11]
>>> print filter(lambda x: x > 0, valores)
[10, 4, 3, 5]

Definimos uma função anônima (portanto, não tem nome), que recebe uma entrada (a variável x) e retorna o resultado da operação x > 0, True ou False.

Poderíamos também usar uma função lambda no exemplo da função reduce():

>>> valores = [1, 2, 3, 4, 5]
>>> soma = reduce(lambda x, y: x + y, valores)
>>> print soma
15

No código acima, definimos uma função anônima que recebe duas entradas e retorna a soma dessas entradas.

[1] Guido Van Rossum. The fate of reduce() in Python 3000