Closures e funções de fábrica
1. Introdução aos Conceitos Fundamentais
1.1. O que são funções de primeira classe em Python
Em Python, funções são cidadãs de primeira classe. Isso significa que podem ser atribuídas a variáveis, passadas como argumentos para outras funções, retornadas como valores e armazenadas em estruturas de dados. Essa característica é fundamental para entender closures e funções de fábrica.
def saudacao(nome):
return f"Olá, {nome}!"
# Função atribuída a uma variável
minha_funcao = saudacao
print(minha_funcao("Maria")) # Olá, Maria!
# Função passada como argumento
def executar(funcao, argumento):
return funcao(argumento)
print(executar(saudacao, "João")) # Olá, João!
1.2. Definição de closure: função interna que "lembra" o escopo léxico
Uma closure é uma função interna que captura e mantém acesso a variáveis do escopo externo, mesmo após a função externa ter encerrado sua execução. Em termos práticos, a closure "lembra" do ambiente onde foi criada.
1.3. Diferença entre closures e funções aninhadas comuns
Nem toda função aninhada é uma closure. Uma função aninhada comum apenas acessa variáveis do escopo externo durante a execução da função externa. Uma closure, por outro lado, retém essas variáveis mesmo após a função externa terminar.
# Função aninhada comum (não é closure)
def externa_comum():
mensagem = "Temporário"
def interna():
return mensagem + " (só durante execução)"
return interna() # Executa imediatamente
# Closure verdadeira
def externa_closure():
mensagem = "Lembrado!"
def interna():
return mensagem + " (mesmo após externa terminar)"
return interna # Retorna a função, não o resultado
closure = externa_closure()
print(closure()) # Lembrado! (mesmo após externa terminar)
2. Anatomia de uma Closure
2.1. Estrutura básica: função externa retornando função interna
def criar_saudacao(saudacao_personalizada):
def saudar(nome):
return f"{saudacao_personalizada}, {nome}!"
return saudar
saudar_ola = criar_saudacao("Olá")
saudar_bom_dia = criar_saudacao("Bom dia")
print(saudar_ola("Ana")) # Olá, Ana!
print(saudar_bom_dia("Carlos")) # Bom dia, Carlos!
2.2. O atributo __closure__ e células de variáveis livres
O Python armazena as variáveis capturadas pela closure no atributo __closure__. Cada célula contém uma variável livre.
def externa():
x = 10
y = 20
def interna():
return x + y
return interna
closure = externa()
print(closure.__closure__) # Mostra as células
print(closure.__closure__[0].cell_contents) # 10
print(closure.__closure__[1].cell_contents) # 20
2.3. Captura de variáveis mutáveis e imutáveis: armadilhas comuns
Variáveis imutáveis (como inteiros e strings) são capturadas por valor. Variáveis mutáveis (como listas e dicionários) são capturadas por referência, o que pode causar comportamentos inesperados.
# Armadilha comum com variáveis mutáveis
def criar_contadores():
contadores = []
for i in range(3):
def contador():
return i # Captura i por referência
contadores.append(contador)
return contadores
contadores = criar_contadores()
print([c() for c in contadores]) # [2, 2, 2] (todos retornam 2!)
# Solução: capturar o valor atual
def criar_contadores_corrigido():
contadores = []
for i in range(3):
def contador(valor=i): # Valor padrão captura o valor atual
return valor
contadores.append(contador)
return contadores
contadores_corrigido = criar_contadores_corrigido()
print([c() for c in contadores_corrigido]) # [0, 1, 2]
3. Funções de Fábrica (Factory Functions)
3.1. Conceito: funções que criam e retornam outras funções
Funções de fábrica são funções que geram e retornam outras funções, geralmente personalizadas com base em parâmetros fornecidos.
3.2. Exemplo prático: fábrica de funções matemáticas
def criar_multiplicador(fator):
def multiplicar(numero):
return numero * fator
return multiplicar
dobro = criar_multiplicador(2)
triplo = criar_multiplicador(3)
print(dobro(5)) # 10
print(triplo(5)) # 15
# Fábrica de operações matemáticas
def criar_operacao(operacao):
def operar(a, b):
if operacao == 'soma':
return a + b
elif operacao == 'subtracao':
return a - b
elif operacao == 'multiplicacao':
return a * b
elif operacao == 'divisao':
return a / b if b != 0 else "Erro: divisão por zero"
return operar
somar = criar_operacao('soma')
dividir = criar_operacao('divisao')
print(somar(10, 5)) # 15
print(dividir(10, 2)) # 5.0
3.3. Personalização de comportamento via parâmetros da fábrica
def criar_validador(tipo_validacao, parametro=None):
def validar(valor):
if tipo_validacao == 'minimo':
return valor >= parametro
elif tipo_validacao == 'maximo':
return valor <= parametro
elif tipo_validacao == 'intervalo':
return parametro[0] <= valor <= parametro[1]
elif tipo_validacao == 'tamanho':
return len(str(valor)) <= parametro
return False
return validar
validar_idade_minima = criar_validador('minimo', 18)
validar_intervalo = criar_validador('intervalo', (0, 100))
print(validar_idade_minima(25)) # True
print(validar_intervalo(150)) # False
4. Closures como Máquinas de Estado Simples
4.1. Mantendo estado privado sem classes
Closures podem armazenar estado interno sem expor variáveis globalmente, funcionando como máquinas de estado simples.
4.2. Exemplo: closure para média móvel com armazenamento interno
def criar_media_movel():
valores = [] # Estado privado
def adicionar_valor(novo_valor):
valores.append(novo_valor)
return sum(valores) / len(valores)
return adicionar_valor
media = criar_media_movel()
print(media(10)) # 10.0
print(media(20)) # 15.0
print(media(30)) # 20.0
4.3. Comparação com abordagem orientada a objetos
# Versão com closure
def criar_contador():
contagem = 0
def incrementar():
nonlocal contagem
contagem += 1
return contagem
return incrementar
contador_closure = criar_contador()
# Versão com classe
class Contador:
def __init__(self):
self.contagem = 0
def incrementar(self):
self.contagem += 1
return self.contagem
contador_classe = Contador()
print(contador_closure()) # 1
print(contador_classe.incrementar()) # 1
5. Aplicações Práticas no Dia a Dia
5.1. Lazy evaluation e computação adiada
def criar_calculador_lazy(operacao, a, b):
def calcular():
if operacao == 'soma':
return a + b
elif operacao == 'potencia':
return a ** b
return None
return calcular
calc_diferido = criar_calculador_lazy('potencia', 2, 10)
# Cálculo só ocorre quando chamado
print(calc_diferido()) # 1024
5.2. Criação de callbacks personalizados
def criar_callback_confirmacao(mensagem, confirmacoes_necessarias=1):
confirmacoes = 0
def callback():
nonlocal confirmacoes
confirmacoes += 1
if confirmacoes >= confirmacoes_necessarias:
return f"Ação confirmada: {mensagem}"
return f"Confirmação {confirmacoes}/{confirmacoes_necessarias}"
return callback
confirmar = criar_callback_confirmacao("Excluir arquivo", 2)
print(confirmar()) # Confirmação 1/2
print(confirmar()) # Ação confirmada: Excluir arquivo
5.3. Cache simples com closures (memoization manual)
def memoizar():
cache = {}
def funcao_memoizada(n):
if n not in cache:
print(f"Calculando fibonacci({n})...")
if n <= 1:
cache[n] = n
else:
cache[n] = funcao_memoizada(n-1) + funcao_memoizada(n-2)
return cache[n]
return funcao_memoizada
fibonacci = memoizar()
print(fibonacci(10)) # Calcula e armazena
print(fibonacci(10)) # Retorna do cache
6. Closures e Escopo de Variáveis
6.1. Regras LEGB e a busca por variáveis não locais
Python busca variáveis na ordem: Local, Enclosing (escopo externo), Global, Built-in. Closures acessam variáveis no escopo "Enclosing".
6.2. A palavra-chave nonlocal e modificação de variáveis em escopo externo
def criar_acumulador():
total = 0
def acumular(valor):
nonlocal total # Permite modificar variável do escopo externo
total += valor
return total
return acumular
acumulador = criar_acumulador()
print(acumulador(5)) # 5
print(acumulador(10)) # 15
print(acumulador(3)) # 18
6.3. Diferenças entre Python 2 e Python 3 para closures
Em Python 2, closures tinham acesso limitado a variáveis de escopo externo e não existia nonlocal. Python 3 introduziu nonlocal para modificar variáveis em escopos externos não globais.
7. Limitações e Boas Práticas
7.1. Consumo de memória: referências circulares e garbage collection
Closures mantêm referências para variáveis do escopo externo, o que pode impedir a coleta de lixo se não forem usadas adequadamente.
7.2. Quando usar closures vs. classes vs. decoradores
- Closures: estado simples, poucos métodos, encapsulamento leve
- Classes: estado complexo, múltiplos métodos, herança necessária
- Decoradores: modificar comportamento de funções existentes
7.3. Debugging de closures: ferramentas e técnicas
import inspect
def criar_funcao():
x = 42
def interna():
return x
return interna
func = criar_funcao()
print(inspect.getclosurevars(func)) # Mostra variáveis capturadas
8. Casos de Uso Avançados e Integração
8.1. Closures em combinadores (ex: partial da biblioteca functools)
from functools import partial
def potencia(base, expoente):
return base ** expoente
quadrado = partial(potencia, expoente=2)
cubo = partial(potencia, expoente=3)
print(quadrado(5)) # 25
print(cubo(5)) # 125
8.2. Integração com decoradores: closures como base dos decoradores
def decorador_tempo(funcao):
import time
def wrapper(*args, **kwargs):
inicio = time.time()
resultado = funcao(*args, **kwargs)
fim = time.time()
print(f"{funcao.__name__} executou em {fim - inicio:.4f} segundos")
return resultado
return wrapper
@decorador_tempo
def operacao_demorada():
return sum(range(1000000))
operacao_demorada()
8.3. Exemplo completo: sistema de plugins com funções de fábrica
class PluginManager:
def __init__(self):
self._plugins = {}
def registrar_plugin(self, nome, fabrica):
self._plugins[nome] = fabrica
def executar_plugin(self, nome, *args, **kwargs):
if nome in self._plugins:
plugin = self._plugins[nome](*args, **kwargs)
return plugin()
raise ValueError(f"Plugin {nome} não encontrado")
# Fábrica de plugins
def fabrica_saudacao(idioma):
def saudar():
saudos = {
'pt': "Olá, mundo!",
'en': "Hello, world!",
'es': "¡Hola, mundo!"
}
return saudos.get(idioma, "Idioma não suportado")
return saudar
manager = PluginManager()
manager.registrar_plugin('saudacao_pt', lambda: fabrica_saudacao('pt'))
manager.registrar_plugin('saudacao_en', lambda: fabrica_saudacao('en'))
print(manager.executar_plugin('saudacao_pt')) # Olá, mundo!
print(manager.executar_plugin('saudacao_en')) # Hello, world!
Referências
- Python Documentation: Naming and binding — Documentação oficial sobre escopo de variáveis e closures em Python
- Real Python: Python Closures — Tutorial completo e prático sobre closures em Python
- GeeksforGeeks: Python Closures — Guia detalhado com exemplos de closures e funções de fábrica
- Programiz: Python Closure — Tutorial interativo explicando closures com exemplos práticos
- PEP 3104: Access to Names in Outer Scopes — Proposta de melhoria que introduziu a palavra-chave
nonlocalem Python 3 - Python Documentation: functools.partial — Documentação oficial sobre a função
partiale combinadores - Medium: Understanding Python Closures — Artigo técnico explicando closures com exemplos do mundo real