Menu fechado

Python3 10 – Zip, Map, Reduce, Filter e Lambda

Ao longo destes textos, algumas das classes builtin do Python1 foram tratadas como funções, apenas pela comodidade de não ter de adentrar em explicações prematuras sobre classes e orientação a objetos. Comandos como list(), tuple(), dict(), complex(), float(), int(), str(), entre outros empregados ao longo destes textos, são todos classes no Python, os quais retornam como objetos uma lista, tupla, dicionário, número complexo, ponto flutuante, inteiro e string, respectivamente.

Neste texto, será tratado de mais algumas classes especiais do Python, como a zip, map e filter, além do comando para criação de funções anônimas, lambda e da função reduce. Estas são ferramentas internas do Python intimamente ligadas a programação funcional.

No Python 3, as classes zip, map e filter geram iteráveis do tipo zip, map e filter, respectivamente, enquanto que no Python 2 estas classes geravam uma lista com os elementos. Por este motivo, estas classes serão associados às classes list, tuple e dict para gerarem objetos visíveis, como lista, tupla e dicionário. Esta escolha no Python 3 se deve à vasta utilização destas classes apenas para serem empregadas como iteradores, ou mesmo em laços de repetição.

1. Classe zip

A classe zip() retorna um objeto do tipo zip, um iterável que associa os elementos dos iteráveis passados como argumentos. Sua sintaxe é apresentada a seguir:

zip(iter1[, iter2[, iter3[...]]])

O comando zip criará um iterador do tipo zip com os elementos tomados de cada iterador passado como argumento. Supondo que se tenha dado o comando zip(iter1, iter2, iter3, ...), sua saída será um iterador semelhante a uma lista de tuplas com os conteúdos: (i11, i21, i31, …), (i21, i22, i23, …), (i31, i32, i33, …), …, onde i11 corresponde ao elemento 1 do iterador 1, o i21 o elementos 1 do iterador 2 e assim por diante.

Os comandos a seguir ilustram a aplicação da classe zip():

>>> L = (1,2,3,4)
>>> zip(L)
<zip object at 0x7ff2f5938288>
>>> list(zip(L))
[(1,), (2,), (3,), (4,)]
>>>
>>> from string import ascii_lowercase, ascii_uppercase
>>> ascii_lowercase, ascii_uppercase
('abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
>>>
>>> List = list(zip(L, ascii_lowercase, ascii_uppercase))
>>> Dic = dict(zip(L, ascii_lowercase))
>>> List
[(1, 'a', 'A'), (2, 'b', 'B'), (3, 'c', 'C'), (4, 'd', 'D')]
>>> Dic
{1: 'a', 2: 'b', 3: 'c', 4: 'd'}

No primeiro comando, é criado um objeto zip com os elementos da lista L e elementos vazios no endereço de memória 0x7ff2f604e488. Sendo um iterável, este objeto pode ser usado para gerar uma lista, tupla, dicionário ou ser empregado em um laço de repetição. A linha list(zip(L)) emprega a classe list para gerar uma lista de tuplas a partir do objeto zip, usando os elementos da lista L e um elemento vazio.

No import seguinte, a biblioteca string é empregada para importar os caracteres ascii minúsculos e maiúsculos (ascii_lowercase, ascii_uppercase). Em seguida, estas strings, também iteradores, são passadas a classe zip para gerar uma lista, List, com as tuplas que associam os elementos da lista L, com os elementos das strings ascii_lowercase e ascii_uppercase.

O dicionário Dic é criado utilizando o iterador zip criado pela lista L e pela string ascii_lowercase, mapeando os quatro primeiros elementos da string ascii_lowercase com os índices da lista L.

2. Classe map

A classe map cria um objeto do tipo map. Sua sintaxe é apresentada a seguir:

map(Função, Iterável)

O objeto gerado será a um iterável cujos elementos são os resultados da aplicação dos elementos do Iterável na Função passada como argumento. Veja os comandos a seguir:

>>> def cubo(x):
... return x**3
...
>>> map(cubo, range(5))
<map object at 0x7f667c44e278>
>>> list(map(cubo, range(5)))
[0, 1, 8, 27, 64]
>>> list(map(cubo, range(10)))
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]

3. Classe filter

A classe filter retorna um objeto iterador, cujos elementos passam por uma função filtro. Sua sintaxe é apresentada a seguir:

filter(Função, Iterável)

O objeto gerado é um iterador cujos elementos são os elementos do Iterável, filtrados pela Função passada como parâmetro. A Função em questão deve retornar True ou False para cada elemento do Iterável. Veja os exemplos a seguir:

>>> def é_par(n):
... return True if n%2 == 0 else False
...
>>> list(filter(é_par, range(11)))
[0, 2, 4, 6, 8, 10]
>>>
>>> def é_primo(n):
... if n in (1, 2):
... return True
... for i in range(2, n):
... if n % i == 0:
... return False
... return True
...
>>> list(filter(é_primo, range(100, 200)))
[101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163,
167, 173, 179, 181, 191, 193, 197, 199]

Primeiro é definida a função é_par(n) para retornar True se n for um número par. Em seguida, esta função é passada para a classe filter, que gera um iterador com todos os pares entre 0 e 10, convertido em uma lista pela classe list.

Na sequência, é empregada a função é_primo() para gerar a lista de números primos entre 100 e 200.

4. Função reduce

Por um dessabor do Guido van Rossum, criador da linguagem Python, a função reduce() foi removida do builtins do Python 3 e transferida a uma biblioteca functools. O texto de 10 de março de 2005 ilustra o ocorrido:

“So now reduce(). This is actually the one I’ve always hated most, because, apart from a few examples involving + or *, almost every time I see a reduce() call with a non-trivial function argument, I need to grab pen and paper to diagram what’s actually being fed into that function before I understand what the reduce() is supposed to do. So in my mind, the applicability of reduce() is pretty much limited to associative operators, and in all other cases it’s better to write out the accumulation loop explicitly.”

Admito estar de acordo com Guido van Rossum. Primeiro a sua sintaxe:

reduce(Func, Iterável)

Neste caso, a Func deve ser uma função de dois parâmetros que gera um único resultado. A função reduce() irá pegar os dois primeiros elementos do Iterável, e1 e e2, e passá-los à função Func, Func(e1, e2). O resultado será passado à função Func novamente, juntamente com o próximo elemento do Iterável, e3, Func(Func(e1, e2), e3). O processo se repete ao próximo elemento do Iterável, Func(Func(Func(e1, e2), e3), e4), e assim por diante até que o último elemento seja consumido. Veja o exemplo a seguir:

>>> def produto(x, y):
... return x*y
...
>>> reduce(produto, [1, 2, 3, 4, 5])
120

O diagrama a seguir ilustra a evolução da função reduce() sobre os elementos da lista passada.

Em suma, a função reduce() aplicada à função produto() e à lista de inteiros de 1 a 5 reproduziu o fatorial de 5.

O somatório também poderia ser implementado pela função reduce() com as linhas abaixo:

>>> def soma(x, y):
... return x+y
...
>>> reduce(soma, range(6))
15

O resultado é a soma dos inteiros 0 + 1 + 2 + 3 + 4 + 5 = 15. O código é apenas ilustrativo, existe a função sum() para isto.

5. Função Anônima lambda

Assim como outras linguagens, o Python possui suporte a funções anônimas através do comando lambda. Funções anônimas geralmente são funções curtas de uso localizado, podendo ser descartadas após seu uso. Diferentemente das funções definidas pelo comando def, as quais necessitam ser atribuídas a um nome após a sua criação, ao criar uma função anônima, o Python apenas retorna uma função sem nome. As linhas abaixo ilustram isto:

>>> soma.__name__
'soma'
>>> produto.__name__
'produto'
>>> SOMA = lambda x, y: x*y
>>> PRODUTO = lambda x, y: x*y
>>> SOMA.__name__
'<lambda>'
>>> PRODUTO.__name__
'<lambda>'

As funções soma e produto, definidas anteriormente, possuem o atributo __name__ com o nome da função no caso soma e produto. Já as funções anônimas atribuídas aos apontadores SOMA e PRODUTO possuem no atributo __nome__ apenas uma referência a funções anônimas, '<lambda>'.

Este conceito e o nome da função são ‘emprestados’ da linguagem Lisp. Sua sintaxe é apresentada a seguir:

lambda Variáveis: Expressão

Onde Variáveis é uma lista de variáveis separadas por vírgula e Expressão é a expressão matemática ou lógica.

Para ilustrar algumas aplicações, veja como algumas das listas anteriores podem ser facilmente criadas com o auxílio do comando lambda:

>>> list(map(lambda x: x**3, range(5)))
[0, 1, 8, 27, 64]
>>>
>>> list(filter(lambda n: True if n%2 == 0 else False, range(11)))
[0, 2, 4, 6, 8, 10]
>>>
>>> reduce(lambda x, y: x*y, range(1, 6))
120
>>>
>>> reduce(lambda x, y: x+y, range(1, 6))
15

6. Conclusões

Estas classes e funções dão novas dimensões à programação em Python, sendo sempre aconselhável dar uma boa olhada nelas antes de iniciar o desenvolvimento de algum código.

Para o próximo texto está sendo preparado um detalhamento mais profundo sobre iteradores e geradores, mas ainda percebo que deveria adicionar mais algum assunto.

  1. Builtin se refere a classes, funções, atributos, … que são implementadas diretamente no interpretador do Python

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

This site uses Akismet to reduce spam. Learn how your comment data is processed.