Closure é uma função que captura variáveis do escopo externo. Ela “lembra” dessas variáveis mesmo após a função externa terminar. Primeiramente, closures são criadas dentro de outra função. Por exemplo, uma função interna que usa variáveis da função mãe. Além disso, closures evitam variáveis globais e encapsulam estado. Assim, você obtém encapsulamento sem classes. Consequentemente, o código fica mais modular e seguro. Quando utilizar closures? Em fábricas de funções personalizadas. Também para criar contadores, acumuladores e decorators. Por outro lado, para estado muito complexo, prefira classes. Closures são a base de muitos padrões funcionais em Python. Então, vamos explorar criação, características e usos práticos. Três subtítulos guiarão você pelo mundo dos closures. Portanto, ao final, você escreverá código mais elegante e encapsulado.
Criando e entendendo closures
Um closure requer uma função aninhada e variáveis externas. A função interna deve referenciar variáveis da função externa. A função externa retorna a interna sem executá-la. Quando usar closures simples? Para criar funções configuráveis. Por exemplo, multiplicadores, somadores ou loggers. Além disso, closures preservam as variáveis capturadas. Exemplo de closures básicos:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# Closure simples: multiplicador def multiplicador(fator): """Retorna uma função que multiplica pelo fator.""" def multiplicar(x): return x * fator return multiplicar print("=== Closures Básicos ===") dobro = multiplicador(2) triplo = multiplicador(3) print(f"dobro(5): {dobro(5)}") print(f"triplo(5): {triplo(5)}") # Inspect do closure print(f"\nClosure dobro: {dobro.__closure__}") print(f"Variável capturada: {dobro.__closure__[0].cell_contents}") # Closure com múltiplas variáveis def criar_potencia(expoente): """Retorna função que eleva à potência especificada.""" def potencia(base): return base ** expoente return potencia quadrado = criar_potencia(2) cubo = criar_potencia(3) print(f"\nquadrado(4): {quadrado(4)}") print(f"cubo(4): {cubo(4)}") # Closure com variável mutável def contador(): """Retorna função que incrementa um contador interno.""" count = 0 def incrementar(): nonlocal count # Necessário para modificar variável externa count += 1 return count return incrementar cont = contador() print(f"\ncont(): {cont()}") print(f"cont(): {cont()}") print(f"cont(): {cont()}") # Verificando se é closure def funcao_normal(x): return x * 2 print(f"\nmultiplicador é closure? {hasattr(multiplicador(2), '__closure__')}") print(f"funcao_normal é closure? {hasattr(funcao_normal, '__closure__')}") |
Closures capturam o ambiente onde foram criados. Eles são uma alternativa elegante a classes simples. Portanto, use closures para funcionalidades pequenas com estado.
Closures com estado e nonlocal
Closures podem manter estado entre chamadas sucessivas.
Use nonlocal para modificar variáveis do escopo externo.
Quando usar closures com estado? Em acumuladores e caches.
Também para geradores simples e funções com memória.
Assim, cada closure mantém seu próprio estado independente.
Exemplo de closures com estado:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# Acumulador com closure def acumulador(inicial=0): """Retorna função que acumula valores.""" total = inicial def adicionar(valor): nonlocal total total += valor return total return adicionar print("=== Closures com Estado ===") acc = acumulador(10) print(f"acc(5): {acc(5)}") print(f"acc(3): {acc(3)}") print(f"acc(2): {acc(2)}") # Múltiplos acumuladores independentes acc1 = acumulador(0) acc2 = acumulador(100) print(f"\nacc1(1): {acc1(1)}") print(f"acc2(1): {acc2(1)}") print(f"acc1(5): {acc1(5)}") print(f"acc2(10): {acc2(10)}") # Cache simples com closure def criar_cache(): """Retorna funções para armazenar e recuperar valores.""" cache = {} def armazenar(chave, valor): cache[chave] = valor return valor def recuperar(chave): return cache.get(chave) def limpar(): cache.clear() return armazenar, recuperar, limpar armazenar, recuperar, limpar = criar_cache() print("\n=== Cache com Closure ===") armazenar("usuario", "Ana") armazenar("idade", 25) print(f"recuperar('usuario'): {recuperar('usuario')}") print(f"recuperar('idade'): {recuperar('idade')}") # Closure para média móvel def media_movel(tamanho): """Calcula média móvel dos últimos N valores.""" valores = [] def adicionar(valor): valores.append(valor) if len(valores) > tamanho: valores.pop(0) return sum(valores) / len(valores) return adicionar media3 = media_movel(3) print("\n=== Média Móvel ===") print(f"media3(10): {media3(10)}") print(f"media3(20): {media3(20)}") print(f"media3(30): {media3(30)}") print(f"media3(40): {media3(40)}") # Closure com configuração def criar_saudacao(saudacao_padrao="Olá"): """Retorna função de saudação configurável.""" def saudacao(nome, saudacao_especifica=None): saudacao_usar = saudacao_especifica or saudacao_padrao return f"{saudacao_usar}, {nome}!" return saudacao saudar_pt = criar_saudacao("Olá") saudar_en = criar_saudacao("Hello") saudar_es = criar_saudacao("Hola") print("\n=== Saudação Configurável ===") print(saudar_pt("Ana")) print(saudar_en("Ana")) print(saudar_es("Ana")) print(saudar_pt("Carlos", "Oi")) |
Closures com nonlocal mantêm estado persistente.
Cada closure tem seu próprio ambiente independente.
Portanto, você pode criar quantas instâncias precisar.
Aplicações práticas de closures
Closures são usados em decorators, callbacks e estratégias. Eles substituem classes pequenas com um único método. Quando usar closures em projetos reais? Em funções de fábrica. Também em injeção de dependências e configuração dinâmica. Além disso, closures são excelentes para callbacks personalizados. Exemplo de aplicações práticas:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# Closure para logging configurável def criar_logger(nivel="INFO"): """Cria um logger com nível configurável.""" def log(mensagem): print(f"[{nivel}] {mensagem}") return log logger_info = criar_logger("INFO") logger_erro = criar_logger("ERRO") logger_debug = criar_logger("DEBUG") print("=== Logger Configurável ===") logger_info("Sistema iniciado") logger_erro("Falha na conexão") logger_debug("Valor calculado: 42") # Closure para rate limiter def rate_limiter(max_chamadas, janela): """Limita chamadas a uma função.""" def decorator(func): chamadas = [] def wrapper(*args, **kwargs): import time agora = time.time() # Remove chamadas antigas while chamadas and chamadas[0] < agora - janela: chamadas.pop(0) if len(chamadas) >= max_chamadas: raise RuntimeError("Limite excedido") chamadas.append(agora) return func(*args, **kwargs) return wrapper return decorator # Uso prático em simulação de API @rate_limiter(max_chamadas=3, janela=5) def api_call(url): return f"Chamando {url}" print("\n=== Rate Limiter ===") import time for i in range(4): try: print(api_call(f"api/item/{i}")) except RuntimeError as e: print(f"Erro: {e}") time.sleep(1) # Closure para validação de campos def criar_validador(campo, tipo_esperado): """Cria um validador para um campo específico.""" def validar(dados): if campo not in dados: raise ValueError(f"Campo '{campo}' ausente") if not isinstance(dados[campo], tipo_esperado): raise TypeError(f"Campo '{campo}' deve ser {tipo_esperado.__name__}") return True return validar valida_nome = criar_validador("nome", str) valida_idade = criar_validador("idade", int) print("\n=== Validação com Closure ===") pessoa = {"nome": "Ana", "idade": 25} print(valida_nome(pessoa)) print(valida_idade(pessoa)) try: valida_idade({"nome": "Carlos", "idade": "trinta"}) except TypeError as e: print(f"Erro: {e}") # Closure para estratégia de ordenação def criar_ordenador(chave): """Cria uma função de ordenação baseada em uma chave.""" def ordenar(lista): return sorted(lista, key=lambda x: x[chave]) return ordenar ordenar_por_idade = criar_ordenador("idade") ordenar_por_nome = criar_ordenador("nome") pessoas = [ {"nome": "Carlos", "idade": 35}, {"nome": "Ana", "idade": 25}, {"nome": "Beatriz", "idade": 30} ] print("\n=== Estratégia de Ordenação ===") print(f"Por nome: {ordenar_por_nome(pessoas)}") print(f"Por idade: {ordenar_por_idade(pessoas)}") # Closure para injeção de dependência def criar_servico(db_connection): """Cria um serviço com dependência injetada.""" def buscar_usuario(usuario_id): # Simula consulta ao banco return f"Usuário {usuario_id} do banco {db_connection}" def salvar_usuario(usuario): return f"Salvo {usuario} em {db_connection}" return buscar_usuario, salvar_usuario db_local = criar_servico("local.db") db_remoto = criar_servico("remoto.db") print("\n=== Injeção de Dependência ===") print(db_local[0](1)) print(db_local[1]("Ana")) print(db_remoto[0](2)) |
Closures são uma ferramenta versátil no arsenal Python. A fórmula de um closure pode ser expressa assim: \(C = \text{função interna} + \text{variáveis externas capturadas}\) Closures evitam classes desnecessárias para estado simples. Eles são mais leves e muitas vezes mais legíveis. Use closures para fábricas de funções e callbacks. Portanto, seu código será mais funcional e encapsulado. Finalmente, pratique criando closures para seus problemas diários.