Funções lambda: expressões anônimas

1. O que são funções lambda?

Funções lambda em Python são funções anônimas definidas em uma única linha, sem a necessidade da palavra-chave def ou de um nome. A sintaxe básica é:

lambda argumentos: expressão

Diferentemente das funções tradicionais definidas com def, lambdas são:

  • Anônimas: não possuem nome próprio (embora possam ser atribuídas a variáveis)
  • Inline: podem ser criadas no local onde são usadas
  • Expressão única: contêm apenas uma expressão, sem statements

Exemplo comparativo:

# Função tradicional
def soma(a, b):
    return a + b

# Lambda equivalente
soma_lambda = lambda a, b: a + b

print(soma(3, 4))        # 7
print(soma_lambda(3, 4)) # 7

2. Sintaxe e regras fundamentais

A estrutura de um lambda é simples: parâmetros, dois-pontos e a expressão a ser avaliada. Não há return explícito — o resultado da expressão é automaticamente retornado.

# Lambda com um argumento
quadrado = lambda x: x ** 2
print(quadrado(5))  # 25

# Lambda com múltiplos argumentos
media = lambda x, y, z: (x + y + z) / 3
print(media(10, 20, 30))  # 20.0

Limitações importantes: lambdas aceitam apenas expressões, não statements. Isso significa que não podemos usar if/else completo, loops for, try/except, etc. Porém, o operador ternário é permitido:

# Ternário funciona em lambda
par_ou_impar = lambda x: "par" if x % 2 == 0 else "ímpar"
print(par_ou_impar(7))  # ímpar

Lambdas também suportam argumentos padrão, *args e **kwargs:

# Argumento padrão
potencia = lambda base, exp=2: base ** exp
print(potencia(3))      # 9
print(potencia(3, 3))   # 27

# *args
soma_tudo = lambda *args: sum(args)
print(soma_tudo(1, 2, 3, 4))  # 10

# **kwargs
info = lambda **kwargs: f"{kwargs.get('nome', 'Desconhecido')} - {kwargs.get('idade', '?')}"
print(info(nome="Ana", idade=30))  # Ana - 30

3. Usando lambda com funções built-in

map() — aplicando transformações

numeros = [1, 2, 3, 4, 5]
dobrados = list(map(lambda x: x * 2, numeros))
print(dobrados)  # [2, 4, 6, 8, 10]

filter() — selecionando elementos

numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pares = list(filter(lambda x: x % 2 == 0, numeros))
print(pares)  # [2, 4, 6, 8, 10]

sorted() — ordenação customizada

palavras = ["casa", "bicicleta", "sol", "computador"]
ordenado_por_tamanho = sorted(palavras, key=lambda x: len(x))
print(ordenado_por_tamanho)  # ['sol', 'casa', 'bicicleta', 'computador']

reduce() — operações acumulativas

from functools import reduce

numeros = [1, 2, 3, 4, 5]
produto = reduce(lambda x, y: x * y, numeros)
print(produto)  # 120

4. Lambda em operações de coleções

Ordenação de listas de dicionários

pessoas = [
    {"nome": "João", "idade": 30},
    {"nome": "Ana", "idade": 25},
    {"nome": "Carlos", "idade": 35}
]

# Ordenar por idade
pessoas_ordenadas = sorted(pessoas, key=lambda p: p["idade"])
print(pessoas_ordenadas)
# [{'nome': 'Ana', 'idade': 25}, {'nome': 'João', 'idade': 30}, {'nome': 'Carlos', 'idade': 35}]

# Ordenar por nome (ordem alfabética reversa)
pessoas_reverso = sorted(pessoas, key=lambda p: p["nome"], reverse=True)
print(pessoas_reverso)
# [{'nome': 'João', 'idade': 30}, {'nome': 'Carlos', 'idade': 35}, {'nome': 'Ana', 'idade': 25}]

Agrupamento com itertools.groupby

from itertools import groupby

dados = [("A", 1), ("B", 2), ("A", 3), ("B", 4), ("C", 5)]
dados_ordenados = sorted(dados, key=lambda x: x[0])

grupos = {}
for chave, grupo in groupby(dados_ordenados, key=lambda x: x[0]):
    grupos[chave] = list(grupo)

print(grupos)
# {'A': [('A', 1), ('A', 3)], 'B': [('B', 2), ('B', 4)], 'C': [('C', 5)]}

5. Lambda e closures

Lambdas podem capturar variáveis do escopo onde são criadas, formando closures:

def multiplicador(n):
    return lambda x: x * n

vezes_3 = multiplicador(3)
print(vezes_3(10))  # 30

Cuidado com late binding em loops:

# Problema: todas as funções se referem ao último valor de i
funcoes = [lambda: i for i in range(5)]
print([f() for f in funcoes])  # [4, 4, 4, 4, 4] (em Python 3)

# Solução: capturar o valor atual com argumento padrão
funcoes_corretas = [lambda x=i: x for i in range(5)]
print([f() for f in funcoes_corretas])  # [0, 1, 2, 3, 4]

Em Python 2, o comportamento de late binding era diferente devido à diferença no escopo de variáveis em list comprehensions.

6. Casos de uso avançados

Lambdas em dicionários para simular switch-case

def operacao(op, a, b):
    operadores = {
        "soma": lambda x, y: x + y,
        "subtracao": lambda x, y: x - y,
        "multiplicacao": lambda x, y: x * y,
        "divisao": lambda x, y: x / y if y != 0 else "Erro: divisão por zero"
    }
    return operadores.get(op, lambda x, y: "Operação inválida")(a, b)

print(operacao("soma", 10, 5))        # 15
print(operacao("divisao", 10, 0))     # Erro: divisão por zero
print(operacao("potencia", 2, 3))     # Operação inválida

Lambdas em decoradores simples

def decorador_com_parametro(tipo):
    return lambda func: lambda *args, **kwargs: f"[{tipo}] {func(*args, **kwargs)}"

@decorador_com_parametro("INFO")
def mensagem(texto):
    return texto

print(mensagem("Sistema iniciado"))  # [INFO] Sistema iniciado

Combinação com functools.partial

from functools import partial

# Lambda com partial para fixar argumentos
potencia_base_10 = partial(lambda base, exp: base ** exp, 10)
print(potencia_base_10(3))  # 1000 (10^3)

7. Boas práticas e armadilhas comuns

Quando usar lambdas

  • Para funções simples e curtas (uma expressão)
  • Como argumento inline para funções como map, filter, sorted
  • Quando a função é usada apenas uma vez

Quando evitar lambdas

  • Para lógica complexa que exige múltiplas linhas
  • Quando a legibilidade é prejudicada
  • Para funções que serão reutilizadas em vários lugares
# Ruim: lambda muito complexo
processar = lambda x: (lambda y: y ** 2)(x) if x > 0 else (lambda z: abs(z) * 2)(x)

# Bom: função nomeada
def processar(x):
    if x > 0:
        return x ** 2
    return abs(x) * 2

Erros de escopo com variáveis mutáveis

# Problema: referência a lista mutável
contadores = []
for i in range(5):
    contadores.append(lambda: i)  # Todas referenciam o mesmo i

# Solução: capturar o valor
contadores = []
for i in range(5):
    contadores.append(lambda x=i: x)

Performance

Lambdas e funções nomeadas têm performance similar para operações simples. A diferença é geralmente irrelevante em aplicações reais:

import timeit

# Lambda
tempo_lambda = timeit.timeit('list(map(lambda x: x*2, range(1000)))', number=10000)
# Função nomeada
tempo_def = timeit.timeit('''
def dobra(x):
    return x*2
list(map(dobra, range(1000)))
''', number=10000)

# A diferença é mínima na prática

8. Alternativas e complementos

Módulo operator

from operator import itemgetter, attrgetter

pessoas = [("Ana", 25), ("João", 30), ("Carlos", 20)]
# Equivalente a lambda x: x[1]
ordenado_por_idade = sorted(pessoas, key=itemgetter(1))
print(ordenado_por_idade)  # [('Carlos', 20), ('Ana', 25), ('João', 30)]

List comprehensions vs. lambda com map/filter

numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Com lambda e filter
pares_filter = list(filter(lambda x: x % 2 == 0, numeros))

# Com list comprehension (mais legível)
pares_comp = [x for x in numeros if x % 2 == 0]

print(pares_filter == pares_comp)  # True

Geralmente, list comprehensions são preferíveis por serem mais legíveis e idiomáticas em Python.

Quando def é mais claro que lambda

# Evite: lambda desnecessário
resultado = sorted(dados, key=lambda x: calcular_prioridade(x))

# Prefira: função nomeada
def prioridade(x):
    return calcular_prioridade(x)

resultado = sorted(dados, key=prioridade)

Referências