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
- Documentação oficial: Expressões lambda — Seção da documentação oficial do Python explicando a sintaxe e semântica de expressões lambda
- Python Lambda Functions - Real Python — Tutorial abrangente sobre funções lambda com exemplos práticos e boas práticas
- PEP 8 - Style Guide for Python Code — Recomendações de estilo que incluem orientações sobre quando usar lambdas
- Python's map(), filter(), and reduce() Functions - GeeksforGeeks — Explicação detalhada do uso de lambda com funções de ordem superior
- functools.reduce() - Python Documentation — Documentação oficial da função reduce e seu uso com lambdas para operações acumulativas
- operator module - Python Documentation — Alternativas ao lambda para operações comuns como itemgetter e attrgetter