__getattr__ sur un module
-
20-09-2019 - |
Question
Comment mettre en œuvre l'équivalent d'un __getattr__
sur une classe, sur un module?
Exemple
Lorsque vous appelez une fonction qui ne je souhaite existe pas dans les attributs définis de manière statique d'un module, pour créer une instance d'une classe dans ce module, et invoquer la méthode sur avec le même nom que échoué dans la recherche d'attribut sur le module .
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")
Ce qui donne:
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
La solution
Il y a quelque temps, Guido a déclaré que toute méthode spéciale sur LookUps
nouvelles classes de style contournent __getattr__
et __getattribute__
. méthodes Dunder avaient déjà travaillé sur des modules - vous pouvez, par exemple, utiliser un module en tant que gestionnaire de contexte en définissant simplement __enter__
et __exit__
, avant que ces astuces brisé .
Récemment, certaines caractéristiques historiques ont fait un retour, le module __getattr__
parmi eux, et donc le hack existant (un module lui-même remplaçant par une classe en sys.modules
au moment de l'importation) devrait être plus nécessaire.
En Python 3.7+, vous utilisez juste une façon évidente. Pour personnaliser l'accès d'attribut sur un module, définir une fonction __getattr__
au niveau du module qui doit accepter un argument (nom de l'attribut), et renvoie la valeur calculée ou soulever une AttributeError
:
# my_module.py
def __getattr__(name: str) -> Any:
...
Cela permettra également des crochets dans « de » importations, à savoir que vous pouvez retourner des objets générés dynamiquement pour des déclarations telles que from my_module import whatever
.
Sur une note connexe, ainsi que le module getattr vous pouvez également définir une fonction __dir__
au niveau du module pour répondre à dir(my_module)
. Voir PEP 562 pour plus de détails.
Autres conseils
Il y a deux problèmes de base que vous utilisez dans ici:
- méthodes de
__xxx__
ne sont regardé sur la classe -
TypeError: can't set attributes of built-in/extension type 'module'
(1): toute solution devrait aussi garder une trace de ce qui a été en cours d'examen module, sinon tous les modules aurait alors le comportement par exemple de substitution; et (2) signifie que (1) est encore impossible ... à moins pas directement.
Heureusement, sys.modules est pas pointilleux sur ce qui se passe là-bas donc une enveloppe fonctionnera, mais seulement pour l'accès au module (c.-à-import somemodule; somemodule.salutation('world')
, pour l'accès même module à peu près devez tirer sur les méthodes de la classe de substitution et les ajouter à globals()
eiher avec une méthode personnalisée de la classe (j'aime utiliser .export()
) ou avec une fonction générique (tels que ceux déjà énumérés comme des réponses) une chose à garder à l'esprit:. si l'emballage crée une nouvelle instance à chaque fois, et la solution est gLOBALS pas, vous vous retrouvez avec un comportement subtilement différent Oh, et vous ne recevez pas d'utiliser les deux en même temps -.. il est l'un ou l'autre
Mise à jour
De Guido van Rossum :
Il est en fait un hack qui est parfois utilisé et recommandé: un module peut définir une classe avec la fonctionnalité désirée, et ensuite à Finalement, elle-même en remplacement de sys.modules avec une instance de cette classe (Ou avec la classe, si vous insistez, mais qui est généralement moins utile). Par exemple:.
# module foo.py
import sys
class Foo:
def funct1(self, <args>): <code>
def funct2(self, <args>): <code>
sys.modules[__name__] = Foo()
Cela fonctionne parce que les mécanismes d'importation permet à activement cette entaille, et en tant que dernière étape tire le module réel de sys.modules, après son chargement. (C'est pas un hasard. Le hack était proposé il y a longtemps et nous avons décidé que nous aimions assez pour le soutenir dans la machines à l'importation.)
Ainsi, la manière d'accomplir ce que mis en place que vous voulez est de créer une seule classe dans votre module, et comme le dernier acte du module avec une sys.modules[__name__]
remplacer instance de votre classe - et vous pouvez maintenant jouer avec __getattr__
/ __setattr__
/ __getattribute__
au besoin.
Notez que si vous utilisez ce quoi que ce soit fonctionnalité autre dans le module, comme globals, d'autres fonctions, etc., seront perdues lors de la cession de sys.modules
est fait -. Alors assurez-vous que tout le nécessaire est à l'intérieur de la classe de remplacement
Ceci est un hack, mais vous pouvez envelopper le module avec une classe:
class Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
# Perform custom logic here
try:
return getattr(self.wrapped, name)
except AttributeError:
return 'default' # Some sensible default
sys.modules[__name__] = Wrapper(sys.modules[__name__])
Nous ne faisons pas habituellement de cette façon.
Ce que nous faisons est la suivante.
class A(object):
....
# The implicit global instance
a= A()
def salutation( *arg, **kw ):
a.salutation( *arg, **kw )
Pourquoi? Alors que l'instance mondiale implicite est visible.
Pour des exemples, regardez le module random
, ce qui crée une instance mondiale implicite de simplifier légèrement les cas d'utilisation où vous voulez un nombre aléatoire « simple » générateur.
Similaire à ce que @ Håvard S proposé, dans un cas où je devais mettre en œuvre un peu de magie sur un module (comme __getattr__
), je définirais une nouvelle classe qui hérite de types.ModuleType
et le mettre dans sys.modules
(remplaçant probablement le module où mon ModuleType
personnalisé a été défini).
Par la __init__.py
de Werkzeug pour une mise en œuvre assez robuste de cela.
Ceci est hackish, mais ...
import types
class A(object):
def salutation(self, accusative):
print "hello", accusative
def farewell(self, greeting, accusative):
print greeting, accusative
def AddGlobalAttribute(classname, methodname):
print "Adding " + classname + "." + methodname + "()"
def genericFunction(*args):
return globals()[classname]().__getattribute__(methodname)(*args)
globals()[methodname] = genericFunction
# set up the global namespace
x = 0 # X and Y are here to add them implicitly to globals, so
y = 0 # globals does not change as we iterate over it.
toAdd = []
def isCallableMethod(classname, methodname):
someclass = globals()[classname]()
something = someclass.__getattribute__(methodname)
return callable(something)
for x in globals():
print "Looking at", x
if isinstance(globals()[x], (types.ClassType, type)):
print "Found Class:", x
for y in dir(globals()[x]):
if y.find("__") == -1: # hack to ignore default methods
if isCallableMethod(x,y):
if y not in globals(): # don't override existing global names
toAdd.append((x,y))
for x in toAdd:
AddGlobalAttribute(*x)
if __name__ == "__main__":
salutation("world")
farewell("goodbye", "world")
Cela fonctionne par itérer sur les tous les objets de l'espace de noms global. Si l'élément est une classe, il parcourt les attributs de classe. Si l'attribut est appelable, il ajoute à l'espace de noms global en fonction.
Il ignore tous les attributs qui contiennent "__".
Je ne l'utiliser dans le code de production, mais il devrait vous aider à démarrer.
Voici mon humble contribution - une légère embellissement de @ réponse très rated Håvard S, mais un peu plus explicite (de sorte qu'il pourrait être acceptable pour @ S. Lott, même si probablement pas assez bon pour l'OP):
import sys
class A(object):
def salutation(self, accusative):
print "hello", accusative
class Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
try:
return getattr(self.wrapped, name)
except AttributeError:
return getattr(A(), name)
_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])
if __name__ == "__main__":
_globals.salutation("world")
Créez votre fichier de module qui a vos classes. Importer le module. Exécutez getattr
sur le module que vous venez d'importer. Vous pouvez faire une importation dynamique à l'aide __import__
et tirer le module de sys.modules.
Voici votre module some_module.py
:
class Foo(object):
pass
class Bar(object):
pass
Et dans un autre module:
import some_module
Foo = getattr(some_module, 'Foo')
Faire cette dynamique:
import sys
__import__('some_module')
mod = sys.modules['some_module']
Foo = getattr(mod, 'Foo')
Dans certaines circonstances, le dictionnaire de globals()
peut suffire, par exemple, vous pouvez instancier une classe par le nom du champ global:
from somemodule import * # imports SomeClass
someclass_instance = globals()['SomeClass']()