Pular para o conteúdo

    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:

    intermediario/cap33_ordem_superior.pylinhas 10 a 13
    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]))
    
    Saída
    [('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:

    intermediario/cap33_ordem_superior.pylinhas 15 a 22
    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"))])
    
    Saída
    ['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:

    intermediario/cap33_ordem_superior.pylinhas 27 a 31
    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__)
    
    Saída
    [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:

    intermediario/cap33_ordem_superior.pylinhas 33 a 39
    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)
    
    Saída
    [('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:

    intermediario/cap33_ordem_superior.pylinhas 44 a 55
    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))
    
    Saída
    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 com quadrado = lambda x: x ** 2. A razão é prática: o def dá 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.