weakref — Referências fracas

Código-fonte: Lib/weakref.py


O módulo weakref permite ao programador Python criar referências fracas para objetos.

A seguir, o termo referente significa o objeto ao qual é referido por uma referência fraca.

Uma referência fraca a um objeto não é suficiente para mantê-lo vivo: quando as únicas referências restantes a um referente são referências fracas, a coleta de lixo está livre para destruir o referente e reutilizar sua memória para outra coisa. Entretanto, até que o objeto seja realmente destruído, a referência fraca poderá retornar o objeto mesmo que não haja referências fortes a ele.

Um uso principal para referências fracas é implementar caches ou mapeamentos contendo objetos grandes, onde é desejado que um objeto grande não seja mantido ativo apenas porque aparece em um cache ou mapeamento.

Por exemplo, se você tiver vários objetos de imagem binária grandes, poderá associar um nome a cada um. Se você usasse um dicionário Python para mapear nomes para imagens, ou imagens para nomes, os objetos de imagem permaneceriam vivos apenas porque apareceriam como valores ou chaves nos dicionários. As classes WeakKeyDictionary e WeakValueDictionary fornecidas pelo módulo weakref são uma alternativa, usando referências fracas para construir mapeamentos que não mantêm objetos vivos apenas porque aparecem nos objetos de mapeamento. Se, por exemplo, um objeto de imagem for um valor em um WeakValueDictionary, então quando as últimas referências restantes a esse objeto de imagem forem as referências fracas mantidas por mapeamentos fracos, a coleta de lixo poderá recuperar o objeto e suas entradas correspondentes em mapeamentos fracos são simplesmente excluídos.

WeakKeyDictionary e WeakValueDictionary usam referências fracas em sua implementação, configurando funções de retorno de chamada nas referências fracas que notificam os dicionários fracos quando uma chave ou valor foi recuperado pela coleta de lixo. WeakSet implementa a interface set, mas mantém referências fracas aos seus elementos, assim como WeakKeyDictionary faz.

finalize fornece uma maneira direta de registrar uma função de limpeza a ser chamada quando um objeto é coletado como lixo. Isso é mais simples de usar do que configurar uma função de retorno de chamada em uma referência fraca não tratada, pois o módulo garante automaticamente que o finalizador permaneça ativo até que o objeto seja coletado.

A maioria dos programas deve descobrir que usar um desses tipos de contêineres fracos ou finalize é tudo que eles precisam – geralmente não é necessário criar suas próprias referências fracas diretamente. O maquinário de baixo nível é exposto pelo módulo weakref para benefício de usos avançados.

Nem todos os objetos podem ser referenciados de maneira fraca. Objetos que oferecem suporte a referências fracas incluem instâncias de classe, funções escritas em Python (mas não em C), métodos de instância, conjuntos, frozensets, alguns objetos arquivos, geradores, objetos tipo, sockets, arrays, deques, objetos padrão de expressão regular e objetos código.

Alterado na versão 3.2: Adicionado suporte para thread.lock, threading.Lock e objetos código.

Vários tipos embutidos como list e dict não oferecem suporta diretamente a referências fracas, mas podem adicionar suporte através de subclasses:

class Dict(dict):
    pass

obj = Dict(red=1, green=2, blue=3)   # este objeto é potencialmente fracamente referenciável

Detalhes da implementação do CPython: Outros tipos embutidos como tuple e int não oferecem suporte a referências fracas mesmo em subclasses.

Os tipos de extensão podem ser facilmente criados para oferecer suporte a referências fracas; veja Weak Reference Support.

Quando __slots__ são definidos para um determinado tipo, o suporte a referência fraca é desativado a menos que uma string '__weakref__' também esteja presente na sequência de strings na declaração de __slots__. Veja a documentação de __slots__ para detalhes.

class weakref.ref(object[, callback])

Retorna uma referência fraca para object. O objeto original pode ser recuperado chamando o objeto referência se o referente ainda estiver ativo; se o referente não estiver mais ativo, chamar o objeto referência fará com que None seja retornado. Se callback for fornecido e não for None, e o objeto referência fraca retornado ainda estiver ativo, o função de retorno será chamada quando o objeto estiver prestes a ser finalizado; o objeto referência fraca será passado como único parâmetro para a função de retorno; o referente não estará mais disponível.

É permitido que muitas referências fracas sejam construídas para o mesmo objeto. As funções de retorno registradas para cada referência fraca serão chamadas da função de retorno registrada mais recentemente para a função de retorno registrada mais antiga.

As exceções levantadas pela função de retorno serão anotadas na saída de erro padrão, mas não poderão ser propagadas; elas são tratadas exatamente da mesma maneira que as exceções levantadas pelo método __del__() de um objeto.

Referências fracas são hasheáveis se o object for hasheável. Elas manterão seu valor de hash mesmo depois que object for excluído. Se hash() for chamada pela primeira vez somente após o object ter sido excluído, a chamada vai levantar TypeError.

Referências fracas oferecem suporte a testes de igualdade, mas não de ordenação. Se os referentes ainda estiverem vivos, duas referências terão a mesma relação de igualdade que seus referentes (independentemente do callback). Se um dos referentes tiver sido excluído, as referências serão iguais somente se os objetos referência forem o mesmo objeto.

Este é um tipo do qual pode ser feita subclasse em vez de uma função de fábrica.

__callback__

Este atributo somente leitura retorna a função de retorno atualmente associada à referência fraca. Se não houver função de retorno ou se o referente da referência fraca não estiver mais ativo, então este atributo terá o valor None.

Alterado na versão 3.4: Adicionado o atributo __callback__.

weakref.proxy(object[, callback])

Retorna um intermediário (proxy, em inglês) para object que usa uma referência fraca. Isto provê suporte ao uso do intermediário na maioria dos contextos, em vez de exigir a desreferenciação explícita usada com objetos de referência fraca. O objeto retornado terá um tipo ProxyType ou CallableProxyType, dependendo se object é chamável. Objetos intermediários não são hasheáveis independentemente do referente; isso evita uma série de problemas relacionados à sua natureza fundamentalmente mutável e impede seu uso como chaves de dicionário. callback é igual ao parâmetro de mesmo nome da função ref().

Acessar um atributo do objeto intermediário depois que o referente é coletado como lixo levanta ReferenceError.

Alterado na versão 3.8: Estendeu o suporte ao operador em objetos intermediários para incluir os operadores @ e @= para multiplicação de matrizes.

weakref.getweakrefcount(object)

Retorna o número de referências fracas e intermediários que fazem referência a object.

weakref.getweakrefs(object)

Retorna uma lista de todos os objetos intermediários e de referência fraca que fazem referência a object.

class weakref.WeakKeyDictionary([dict])

Classe de mapeamento que faz referência fraca a chaves. Entradas no dicionário serão descartadas quando não houver mais uma referência forte à chave. Isso pode ser usado para associar dados adicionais a um objeto de propriedade de outras partes de uma aplicação sem adicionar atributos a esses objetos. Isso pode ser especialmente útil com objetos que substituem acessos de atributos.

Note que quando uma chave com valor igual a uma chave existente (mas não identidade igual) é inserida no dicionário, ela substitui o valor, mas não substitui a chave existente. Devido a isso, quando a referência à chave original é excluída, ela também exclui a entrada no dicionário:

>>> class T(str): pass
...
>>> k1, k2 = T(), T()
>>> d = weakref.WeakKeyDictionary()
>>> d[k1] = 1   # d = {k1: 1}
>>> d[k2] = 2   # d = {k1: 2}
>>> del k1      # d = {}

Uma solução alternativa seria remover a chave antes da reatribuição:

>>> class T(str): pass
...
>>> k1, k2 = T(), T()
>>> d = weakref.WeakKeyDictionary()
>>> d[k1] = 1   # d = {k1: 1}
>>> del d[k1]
>>> d[k2] = 2   # d = {k2: 2}
>>> del k1      # d = {k2: 2}

Alterado na versão 3.9: Adicionado suporte para operadores | e |=, conforme especificado na PEP 584.

Objetos WeakKeyDictionary têm um método adicional que expõe as referências internas diretamente. Não há garantia de que as referências estejam “ativas” no momento em que são usadas, então o resultado da chamada das referências precisa ser verificado antes de ser usado. Isso pode ser usado para evitar a criação de referências que farão com que o coletor de lixo mantenha as chaves por mais tempo do que o necessário.

WeakKeyDictionary.keyrefs()

Retorna um iterável das referências fracas às chaves.

class weakref.WeakValueDictionary([dict])

Classe de mapeamento que faz referência fraca a valores. Entradas no dicionário serão descartadas quando nenhuma referência forte ao valor existir mais.

Alterado na versão 3.9: Adicionado suporte para operadores | e |=, conforme especificado na PEP 584.

Os objetos WeakValueDictionary têm um método adicional que apresenta os mesmos problemas que o método WeakKeyDictionary.keyrefs().

WeakValueDictionary.valuerefs()

Retorna um iterável das referências fracas aos valores.

class weakref.WeakSet([elements])

Define a classe que mantém referências fracas para seus elementos. Um elemento será descartado quando nenhuma referência forte a ele existir mais.

class weakref.WeakMethod(method[, callback])

Uma subclasse personalizada de ref que simula uma referência fraca a um método vinculado (ou seja, um método definido em uma classe e pesquisado em uma instância). Como um método vinculado é efêmero, uma referência fraca padrão não pode mantê-lo. WeakMethod tem um código especial para recriar o método vinculado até que o objeto ou a função original morra:

>>> class C:
...     def method(self):
...         print("method called!")
...
>>> c = C()
>>> r = weakref.ref(c.method)
>>> r()
>>> r = weakref.WeakMethod(c.method)
>>> r()
<bound method C.method of <__main__.C object at 0x7fc859830220>>
>>> r()()
method called!
>>> del c
>>> gc.collect()
0
>>> r()
>>>

callback é o mesmo que o parâmetro de mesmo nome da função ref().

Adicionado na versão 3.4.

class weakref.finalize(obj, func, /, *args, **kwargs)

Retorna um objeto finalizador chamável que será chamado quando obj for coletado como lixo. Diferentemente de uma referência fraca comum, um finalizador sempre sobreviverá até que o objeto referência seja coletado, simplificando muito o gerenciamento do ciclo de vida.

Um finalizador é considerado alive (vivo) até ser chamado (explicitamente ou na coleta de lixo), e depois disso ele é dead (morto). Chamar um finalizador vivo retorna o resultado da avaliação de func(*arg, **kwargs), enquanto chamar um finalizador morto retorna None.

Exceções levantadas por funções de retorno do finalizador durante a coleta de lixo serão mostradas na saída de erro padrão, mas não podem ser propagadas. Elas são tratadas da mesma forma que exceções levantadas de um método __del__() de um objeto ou de uma função de retorno da referência fraca.

Quando o programa é encerrado, cada finalizador vivo restante é chamado, a menos que seu atributo atexit tenha sido definido como falso. Eles são chamados na ordem reversa da criação.

Um finalizador nunca vai invocar sua função de retorno durante a parte posterior do desligamento do interpretador quando os globais do módulo podem ter sido substituídos por None.

__call__()

Se self estiver vivo, marca-o como morto e retorna o resultado da chamada func(*args, **kwargs). Se self estiver morto, retorna None.

detach()

Se self estiver vivo, marca-o como morto e retorna a tupla (obj, func, args, kwargs). Se self estiver morto, retorna None.

peek()

Se self estiver vivo, retorna a tupla (obj, func, args, kwargs). Se self estiver morto, retorna None.

alive

Propriedade que é verdadeiro se o finalizador estiver ativo, falsa caso contrário.

atexit

Uma propriedade booleana gravável que por padrão é verdadeiro. Quando o programa sai, ele chama todos os finalizadores vivos restantes para os quais atexit é verdadeiro. Eles são chamados na ordem reversa da criação.

Nota

É importante garantir que func, args e kwargs não possuam nenhuma referência a obj, direta ou indiretamente, pois, caso contrário, obj nunca será coletado como lixo. Em particular, func não deve ser um método vinculado de obj.

Adicionado na versão 3.4.

weakref.ReferenceType

O objeto tipo para objetos referências fracas.

weakref.ProxyType

O tipo de objeto para intermediários de objetos que não são chamáveis.

weakref.CallableProxyType

O objeto de tipo para intermediários de objetos chamáveis.

weakref.ProxyTypes

Sequência contendo todos os objetos de tipo para intermediários. Isso pode tornar mais simples testar se um objeto é um intermediário sem depender da nomeação de ambos os tipos de intermediário.

Ver também

PEP 205 - Referências fracas

A proposta e a justificativa para esse recurso, incluindo links para implementações anteriores e informações sobre recursos semelhantes em outras linguagens.

Objetos referência fraca

Objetos referência fraca não têm métodos nem atributos além de ref.__callback__. Um objeto referência fraca permite que o referente seja obtido, se ele ainda existir, chamando-o:

>>> import weakref
>>> class Object:
...     pass
...
>>> o = Object()
>>> r = weakref.ref(o)
>>> o2 = r()
>>> o is o2
True

Se o referente não existir mais, chamar o objeto referência retornará None:

>>> del o, o2
>>> print(r())
None

O teste de se um objeto referência fraca ainda está vivo deve ser feito usando a expressão ref() is not None. Normalmente, o código da aplicação que precisa usar um objeto de referência deve seguir este padrão:

# r é um objeto referência fraca
o = r()
if o is None:
    # referente foi coletado como lixo
    print("O objeto foi desalocado; não é possível mexer com ele.")
else:
    print("O objeto ainda está vivo!")
    o.faz_algo_útil()

Usar um teste separado para “vivacidade” cria condições de corrida em aplicações que usam mais de uma thread; uma outra thread pode fazer com que uma referência fraca seja invalidada antes que a referência fraca seja chamada; o idioma mostrado acima é seguro em aplicações que usam mais de uma thread, bem como em aplicações de thread única.

Versões especializadas de objetos ref podem ser criadas por meio de subclasse. Isso é usado na implementação do WeakValueDictionary para reduzir a sobrecarga de memória para cada entrada no mapeamento. Isso pode ser mais útil para associar informações adicionais a uma referência, mas também pode ser usado para inserir processamento adicional em chamadas para recuperar o referente.

Este exemplo mostra como uma subclasse de ref pode ser usada para armazenar informações adicionais sobre um objeto e afetar o valor retornado quando o referente é acessado:

import weakref

class ExtendedRef(weakref.ref):
    def __init__(self, ob, callback=None, /, **annotations):
        super().__init__(ob, callback)
        self.__counter = 0
        for k, v in annotations.items():
            setattr(self, k, v)

    def __call__(self):
        """Retorna um par contendo o referente e o número de
        vezes que a referência foi chamada.
        """
        ob = super().__call__()
        if ob is not None:
            self.__counter += 1
            ob = (ob, self.__counter)
        return ob

Exemplo

Este exemplo simples mostra como uma aplicação pode usar IDs de objeto para recuperar objetos que ele viu antes. Os IDs dos objetos podem então ser usados ​​em outras estruturas de dados sem forçar os objetos a permanecerem vivos, mas os objetos ainda podem ser recuperados por ID se o fizerem.

import weakref

_id2obj_dict = weakref.WeakValueDictionary()

def remember(obj):
    oid = id(obj)
    _id2obj_dict[oid] = obj
    return oid

def id2obj(oid):
    return _id2obj_dict[oid]

Objetos finalizadores

O principal benefício de usar finalize é que ele simplifica o registro de um retorno de chamada sem precisar preservar o objeto finalizador retornado. Por exemplo

>>> import weakref
>>> class Object:
...     pass
...
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")  
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!

O finalizador pode ser chamado diretamente também. No entanto, o finalizador vai invocar a função de retorno no máximo uma vez.

>>> def callback(x, y, z):
...     print("CALLBACK")
...     return x + y + z
...
>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> assert f.alive
>>> assert f() == 6
CALLBACK
>>> assert not f.alive
>>> f()                     # callback not called because finalizer dead
>>> del obj                 # callback not called because finalizer dead

Você pode desfazer o registro de um finalizador usando seu método detach(). Isso elimina o finalizador e retorna os argumentos passados ​​ao construtor quando ele foi criado.

>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> f.detach()                                           
(<...Object object ...>, <function callback ...>, (1, 2), {'z': 3})
>>> newobj, func, args, kwargs = _
>>> assert not f.alive
>>> assert newobj is obj
>>> assert func(*args, **kwargs) == 6
CALLBACK

A menos que você defina o atributo atexit como False, um finalizador será chamado quando o programa sair se ele ainda estiver vivo. Por exemplo

>>> obj = Object()
>>> weakref.finalize(obj, print, "obj dead or exiting")
<finalize object at ...; for 'Object' at ...>
>>> exit()
obj dead or exiting

Comparando finalizadores com métodos __del__()

Suponha que queremos criar uma classe cujas instâncias representam diretórios temporários. Os diretórios devem ser excluídos com seus conteúdos quando o primeiro dos seguintes eventos ocorrer:

  • o objeto é um lixo coletado,

  • o método remove() do objeto é chamado, ou

  • o programa finaliza.

Podemos tentar implementar a classe usando um método __del__() da seguinte maneira:

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()

    def remove(self):
        if self.name is not None:
            shutil.rmtree(self.name)
            self.name = None

    @property
    def removed(self):
        return self.name is None

    def __del__(self):
        self.remove()

A partir do Python 3.4, os métodos __del__() não impedem mais que os ciclos de referência sejam coletados como lixo, e os módulos globais não são mais forçados a None durante desligamento do interpretador. Então, esse código deve funcionar sem problemas no CPython.

Entretanto, o tratamento dos métodos __del__() é notoriamente específico da implementação, pois depende de detalhes internos da implementação do coletor de lixo do interpretador.

Uma alternativa mais robusta pode ser definir um finalizador que faça referência apenas às funções e objetos específicos de que necessita, em vez de ter acesso ao estado completo do objeto:

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()
        self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)

    def remove(self):
        self._finalizer()

    @property
    def removed(self):
        return not self._finalizer.alive

Definido assim, nosso finalizador recebe apenas uma referência aos detalhes que ele precisa para limpar o diretório apropriadamente. Se o objeto nunca for coletado como lixo, o finalizador ainda será chamado na saída.

A outra vantagem dos finalizadores baseados em referências fracas é que eles podem ser usados ​​para registrar finalizadores para classes onde a definição é controlada por terceiros, como executar código quando um módulo é descarregado:

import weakref, sys
def unloading_module():
    # referência implícita aos globais do módulo do corpo da função
weakref.finalize(sys.modules[__name__], unloading_module)

Nota

Se você criar um objeto finalizador em uma thread em um daemon assim que o programa sair, então há a possibilidade de que o finalizador não seja chamado na saída. No entanto, em um thread em um daemon atexit.register(), try: ... finally: ... e with: ... não garantem que a limpeza ocorra também.