Recursos externos como arquivos temporários, handles nativos, sockets auxiliares e registros em caches precisam ser liberados. O caminho principal deve usar context managers e métodos explícitos, mas algumas bibliotecas também precisam de uma rede de segurança quando o objeto é coletado sem fechamento. weakref.finalize registra um callback que permanece vivo sem manter o objeto observado vivo e é executado quando esse objeto se torna inalcançável.
Neste guia, você aprenderá a criar finalizers, evitar referências fortes acidentais, executar a limpeza antecipadamente, usar detach(), verificar alive, entender o comportamento no encerramento do interpretador e decidir quando não depender da coleta de lixo.
Primeiro finalizer
import weakref
class Recurso:
pass
def limpar(nome):
print("limpando", nome)
recurso = Recurso()
finalizer = weakref.finalize(recurso, limpar, "temporário")
Enquanto recurso estiver vivo, o callback não é chamado. Quando o objeto se torna inalcançável, o finalizer pode executar limpar("temporário").
O finalizer não mantém o objeto vivo
A associação usa referência fraca. Contudo, os argumentos do callback são mantidos fortemente. Se algum argumento referencia o próprio objeto, você cria uma cadeia que pode impedir a coleta.
# Evite isto:
weakref.finalize(recurso, recurso.fechar)
Um método vinculado mantém referência forte à instância. Prefira uma função independente que receba apenas os dados necessários:
weakref.finalize(recurso, fechar_handle, recurso.handle)
Callback idempotente
A limpeza deve tolerar chamadas repetidas ou coordenar estado para que o recurso seja liberado uma única vez. Embora o mesmo objeto finalizer execute seu callback no máximo uma vez, o código também pode oferecer close() explícito.
Executar antecipadamente
finalizer()
Chamar o objeto finalizer executa o callback imediatamente se ainda estiver ativo. O valor retornado pelo callback é devolvido. Chamadas posteriores não executam novamente.
Propriedade alive
if finalizer.alive:
finalizer()
alive indica se o callback ainda está registrado e não foi executado nem destacado.
detach
dados = finalizer.detach()
detach() desativa o finalizer e, se ele estava vivo, devolve uma tupla contendo objeto, função, argumentos e kwargs. Use-o para transferir responsabilidade de limpeza para outro componente.
peek
dados = finalizer.peek()
peek() inspeciona o registro sem desativá-lo. O resultado pode conter uma referência forte temporária ao objeto; descarte-a rapidamente.
Finalizer e close explícito
class ArquivoTemporario:
def __init__(self, caminho):
self.caminho = caminho
self._finalizer = weakref.finalize(
self,
remover_arquivo,
caminho,
)
def close(self):
self._finalizer()
O método explícito oferece liberação determinística. O finalizer atua somente como fallback.
Context manager continua preferível
with ArquivoTemporario(caminho) as recurso:
usar(recurso)
Um context manager garante limpeza no fim do bloco mesmo quando ocorre exceção. A coleta de lixo não possui prazo garantido em todas as implementações e situações.
Por que não usar __del__
__del__ pode complicar herança, ciclos, exceções e estado parcial do interpretador. weakref.finalize separa a função de limpeza do objeto e oferece controle por alive, chamada e detach.
Tempo de execução não garantido
Não baseie correção crítica no momento em que o callback será chamado. Referências podem permanecer vivas em caches, closures, tracebacks ou threads. Em outras implementações de Python, a coleta pode ocorrer mais tarde que no CPython.
Coleta cíclica
Finalizers foram projetados para funcionar de forma mais robusta com o coletor, mas o callback ainda deve evitar ressuscitar objetos ou depender de uma ordem específica entre vários recursos.
Ordem de finalização
Quando vários objetos ficam inalcançáveis juntos, não construa uma cadeia que exija ordem implícita. Modele dependências explicitamente: um gerenciador deve fechar filhos antes de si mesmo.
Encerramento do interpretador
Finalizers vivos podem ser executados na saída, normalmente em ordem inversa de criação. A propriedade atexit controla essa participação:
finalizer.atexit = False
No shutdown, módulos e globais podem já estar parcialmente desmontados. Passe ao callback funções e valores independentes em vez de procurar nomes globais tardiamente.
Exceções no callback
Exceções de finalização não podem ser propagadas de forma normal ao código que causou a coleta. Mantenha callbacks pequenos, capture falhas esperadas e registre com segurança. Não faça operações complexas de rede como única forma de persistir dados importantes.
Threads
O callback pode ocorrer em um contexto que não corresponde à thread que criou o objeto. Evite assumir thread-local state. Se um recurso exige liberação em uma thread específica, agende a ação explicitamente no executor correto.
Asyncio
Um finalizer é síncrono e não pode executar await. Não tente fechar diretamente um recurso assíncrono com coroutine. Ofereça async with e aclose(); no máximo, use o finalizer para emitir aviso ou sinalizar um loop ainda ativo com extremo cuidado.
Arquivos temporários
from pathlib import Path
import weakref
class Artefato:
def __init__(self, caminho: Path):
self.caminho = caminho
self._cleanup = weakref.finalize(
self,
Path.unlink,
caminho,
missing_ok=True,
)
def remover(self):
self._cleanup()
A função e o caminho não referenciam a instância. missing_ok=True torna a operação idempotente quando o arquivo já foi removido.
Handles nativos
Ao trabalhar com C, mantenha no callback apenas o identificador necessário e uma função segura para liberá-lo. Verifique se a biblioteca nativa ainda está carregada durante o shutdown.
Objetos em caches
Se um cache mantém referência forte ao objeto, o finalizer não executa. Considere WeakValueDictionary ou uma política explícita de eviction. O artigo sobre weakref no Python explica caches fracos.
Testando finalização
Testes não devem depender apenas de gc.collect(). Teste o caminho explícito chamando close() ou o próprio finalizer e verifique idempotência. Um teste separado pode confirmar o fallback com referência fraca.
finalizer()
assert not finalizer.alive
finalizer() # não repete
Evite capturar self em closure
# Incorreto: a closure captura self
weakref.finalize(self, lambda: liberar(self.handle))
Copie o handle para uma variável independente e não capture a instância.
Transferindo propriedade
Quando um recurso passa para outro objeto, use detach() no proprietário antigo e registre um novo finalizer no novo proprietário. Isso evita duas limpezas concorrentes.
Observabilidade
Um callback fallback pode emitir uma métrica indicando que o recurso não foi fechado explicitamente. Evite logs ruidosos em shutdown e não inclua segredos ou objetos completos.
Desempenho
Finalizers possuem custo de registro e manutenção. Não crie um para cada objeto minúsculo de alta frequência se a limpeza puder ser gerenciada em lote por um proprietário maior.
Erros comuns
- Passar método vinculado do objeto: mantém a instância viva.
- Usar como limpeza principal: prefira close e context manager.
- Executar coroutine no callback: finalizer é síncrono.
- Depender da ordem de vários callbacks: modele a dependência explicitamente.
- Usar globais no shutdown: módulos podem estar desmontados.
- Ignorar idempotência: caminhos explícito e fallback precisam coordenar.
Exemplo completo: recurso com fallback
import weakref
class ConexaoNativa:
def __init__(self, api):
handle = api.abrir()
self._api = api
self._handle = handle
self._finalizer = weakref.finalize(
self,
api.fechar,
handle,
)
@property
def fechada(self):
return not self._finalizer.alive
def close(self):
self._finalizer()
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
self.close()
O uso normal é determinístico com with. Se o consumidor esquecer, o finalizer ainda tenta liberar o handle sem referenciar a instância.
Conclusão
weakref.finalize oferece uma rede de segurança controlável para limpeza associada ao ciclo de vida de um objeto. Ele evita várias fragilidades de __del__, mas não transforma coleta de lixo em gerenciamento determinístico.
A documentação oficial de weakref.finalize detalha a API. Use context managers como caminho principal, não capture o objeto no callback e mantenha a finalização pequena, idempotente e independente do shutdown.







