Como implementar o equivalente a __getattr__
em uma classe, em um módulo?
Exemplo
Ao chamar uma função que não existe nos atributos estaticamente definidos de um módulo, desejo criar uma instância de uma classe nesse módulo e chamar o método nele com o mesmo nome que falhou na pesquisa de atributos no módulo.
class A(object):
def salutation(self, accusative):
print "hello", accusative
# note this function is intentionally on the module, and not the class above
def __getattr__(mod, name):
return getattr(A(), name)
if __name__ == "__main__":
# i hope here to have my __getattr__ function above invoked, since
# salutation does not exist in the current namespace
salutation("world")
Que dá:
matt@stanley:~/Desktop$ python getattrmod.py
Traceback (most recent call last):
File "getattrmod.py", line 9, in <module>
salutation("world")
NameError: name 'salutation' is not defined
python
module
python-3.x
getattr
attributeerror
Matt Joiner
fonte
fonte
salutation
existir no espaço de nomes global ou local (que é o que o código acima está tentando fazer) ou deseja uma pesquisa dinâmica de nomes quando você acessa um ponto em um módulo? São duas coisas diferentes.__getattr__
em módulos é suportado no Python 3.7Respostas:
Há um tempo atrás, Guido declarou que todas as pesquisas de métodos especiais em classes de novo estilo ignoram
__getattr__
e__getattribute__
. Os métodos Dunder já haviam trabalhado nos módulos - você poderia, por exemplo, usar um módulo como um gerenciador de contexto simplesmente definindo__enter__
e__exit__
antes que esses truques quebrassem .Recentemente, alguns recursos históricos retornaram, o módulo
__getattr__
entre eles e, portanto, o hack existente (um módulo que se substitui por uma classe nosys.modules
momento da importação) não é mais necessário.No Python 3.7+, você apenas usa a maneira óbvia. Para personalizar o acesso ao atributo em um módulo, defina uma
__getattr__
função no nível do módulo que aceite um argumento (nome do atributo) e retorne o valor calculado ou aumenteAttributeError
:Isso também permitirá ganchos nas importações "de", ou seja, você pode retornar objetos gerados dinamicamente para instruções como
from my_module import whatever
.Em uma nota relacionada, juntamente com o módulo getattr, você também pode definir uma
__dir__
função no nível do módulo para responderdir(my_module)
. Veja PEP 562 para detalhes.fonte
m = ModuleType("mod")
e definirm.__getattr__ = lambda attr: return "foo"
; no entanto, quando corrofrom mod import foo
, receboTypeError: 'module' object is not iterable
.m.__getattr__ = lambda attr: "foo"
, mais você precisa definir uma entrada para o módulosys.modules['mod'] = m
. Depois, não há erro comfrom mod import foo
.my_module.whatever
para invocá-la (após umimport my_module
).Há dois problemas básicos que você está enfrentando aqui:
__xxx__
métodos são pesquisados apenas na classeTypeError: can't set attributes of built-in/extension type 'module'
(1) significa que qualquer solução também precisaria acompanhar qual módulo estava sendo examinado; caso contrário, cada módulo teria o comportamento de substituição de instância; e (2) significa que (1) nem sequer é possível ... pelo menos não diretamente.
Felizmente, sys.modules não é exigente quanto ao que existe lá, então um wrapper funcionará, mas apenas para acesso ao módulo (ou seja
import somemodule; somemodule.salutation('world')
, para acesso ao mesmo módulo, você praticamente precisará retirar os métodos da classe de substituição e adicioná-los aglobals()
cada método personalizado na classe (eu gosto de usar.export()
) ou com uma função genérica (como as que já estão listadas como respostas). Um aspecto a ser lembrado: se o wrapper estiver criando uma nova instância a cada vez e a solução global não estiver, você acaba com um comportamento sutilmente diferente, e não consegue usar os dois ao mesmo tempo - é um ou outro.Atualizar
Partida Guido van Rossum :
Portanto, a maneira estabelecida de realizar o que você deseja é criar uma única classe no seu módulo e, como o último ato do módulo, substituir
sys.modules[__name__]
por uma instância da sua classe - e agora você pode jogar com__getattr__
/__setattr__
/__getattribute__
conforme necessário.Nota 1 : Se você usar essa funcionalidade, qualquer outra coisa no módulo, como globais, outras funções etc., será perdida quando a
sys.modules
atribuição for feita - portanto, verifique se tudo o que é necessário está dentro da classe de substituição.Nota 2 : Para dar suporte,
from module import *
você deve ter__all__
definido na classe; por exemplo:Dependendo da sua versão do Python, pode haver outros nomes para omitir
__all__
. Oset()
pode ser omitido se a compatibilidade Python 2 não é necessário.fonte
import sys
darNone
parasys
. Eu estou supondo que este hack não é sancionada em Python 2.Isso é um hack, mas você pode agrupar o módulo com uma classe:
fonte
import *
.Geralmente não fazemos dessa maneira.
O que fazemos é isso.
Por quê? Para que a instância global implícita seja visível.
Por exemplo, observe o
random
módulo, que cria uma instância global implícita para simplificar levemente os casos de uso em que você deseja um gerador de números aleatórios "simples".fonte
Semelhante ao que o @ Håvard S propôs, em um caso em que eu precisava implementar alguma mágica em um módulo (como
__getattr__
), eu definiria uma nova classe que herdatypes.ModuleType
e a colocasys.modules
(provavelmente substituindo o módulo em que meu costumeModuleType
foi definido).Veja o
__init__.py
arquivo principal do Werkzeug para uma implementação bastante robusta disso.fonte
Isso é tolice, mas ...
Isso funciona iterando sobre todos os objetos no espaço para nome global. Se o item for uma classe, ele itera sobre os atributos da classe. Se o atributo puder ser chamado, ele será adicionado ao namespace global como uma função.
Ele ignora todos os atributos que contêm "__".
Eu não usaria isso no código de produção, mas deve começar.
fonte
AddGlobalAttribute()
quando há um nível de móduloAttributeError
- tipo o inverso da lógica do @ Håvard S. Se eu tiver uma chance, testarei isso e adicionarei minha própria resposta híbrida, mesmo que o OP já tenha aceitado essa resposta.NameError
exceções para o espaço de nome global (módulo) - o que explica por que essa resposta adiciona chamadas para todas as possibilidades encontradas no espaço para nome global atual para cobrir todos os casos possíveis com antecedência.Aqui está minha humilde contribuição - um leve embelezamento da resposta altamente cotada do @ Håvard S, mas um pouco mais explícita (portanto, pode ser aceitável para o @ S.Lott, embora provavelmente não seja bom o suficiente para o OP):
fonte
Crie seu arquivo de módulo que possui suas classes. Importe o módulo. Execute
getattr
no módulo que você acabou de importar. Você pode fazer uma importação dinâmica usando__import__
e puxando o módulo de sys.modules.Aqui está o seu módulo
some_module.py
:E em outro módulo:
Fazendo isso dinamicamente:
fonte