Capítulo 33, Intermediário
Funções de ordem superior
Funções que recebem funções, ou devolvem funções. É aqui que ordenar, filtrar e transformar dados fica expressivo.
Código deste capítulo: intermediario/cap33_ordem_superior.py
lambda e a chave de ordenação
Uma lambda é uma função anônima de uma única expressão. O uso mais comum é como argumento key de sorted, min e max, que dizem por qual critério comparar:
pessoas = [("Ana", 31), ("Bia", 25), ("Caio", 31)]
print(sorted(pessoas, key=lambda p: p[1]))
print(sorted(pessoas, key=lambda p: (-p[1], p[0])))
print(max(pessoas, key=lambda p: p[1]))
[('Bia', 25), ('Ana', 31), ('Caio', 31)]
[('Ana', 31), ('Caio', 31), ('Bia', 25)]
('Ana', 31)
A ordenação do Python é estável: itens com a mesma chave mantêm a ordem original. E uma tupla como chave ordena por vários critérios; o sinal de menos inverte a ordem de um campo numérico.
Para chaves simples, o módulo operator evita a lambda:
from operator import itemgetter
produtos = [
{"nome": "caneta", "preco": 3.5},
{"nome": "caderno", "preco": 18.9},
{"nome": "lápis", "preco": 1.2},
]
print([p["nome"] for p in sorted(produtos, key=itemgetter("preco"))])
['lápis', 'caneta', 'caderno']
map, filter e zip
O map aplica uma função a cada item e o filter mantém os itens que passam em um teste. Os dois são preguiçosos: devolvem iteradores, e o list() é quem consome:
numeros = [1, 2, 3, 4, 5]
dobrados = list(map(lambda n: n * 2, numeros))
pares = list(filter(lambda n: n % 2 == 0, numeros))
print(dobrados, pares)
print(type(map(str, numeros)).__name__)
[2, 4, 6, 8, 10] [2, 4]
map
Na prática, eu prefiro compreensões a map e filter com lambda, porque dizem a mesma coisa de forma mais legível. O map ainda vale quando a função já existe (map(str, numeros)).
O zip junta sequências em pares e para na mais curta, sem avisar. Desde o Python 3.10, o parâmetro strict=True transforma esse silêncio em erro:
nomes = ["Ana", "Bia", "Caio"]
notas = [9, 8]
print(list(zip(nomes, notas)))
try:
list(zip(nomes, notas, strict=True))
except ValueError as erro:
print(erro)
[('Ana', 9), ('Bia', 8)]
zip() argument 2 is shorter than argument 1
reduce e partial
O reduce acumula uma sequência em um único valor. O partial "congela" alguns argumentos de uma função e devolve uma nova:
from functools import partial, reduce
import operator
def potencia(base, expoente):
return base ** expoente
quadrado = partial(potencia, expoente=2)
cubo = partial(potencia, expoente=3)
print(reduce(operator.mul, [1, 2, 3, 4], 1))
print(quadrado(5), cubo(2))
24
25 8
Não atribua uma lambda a um nome
A PEP 8 recomenda que uma função com nome seja definida com
def, e não comquadrado = lambda x: x ** 2. A razão é prática: odefdá nome à função nas mensagens de erro e permite docstring. A lambda é para uso inline, como argumento.
Exercício 1
Ordenar alunos por dois critérios
Escreva ordenar_alunos(alunos) que receba uma lista de dicionários com nome e nota e ordene pela maior nota e, em caso de empate, pelo nome.