Comment implémenter l'équivalent d'un __getattr__
sur une classe, sur un module?
Exemple
Lors de l'appel d'une fonction qui n'existe pas dans les attributs définis statiquement d'un module, je souhaite créer une instance d'une classe dans ce module et invoquer la méthode dessus avec le même nom que celui qui a échoué dans la recherche d'attributs 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")
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
python
module
python-3.x
getattr
attributeerror
Matt Joiner
la source
la source
salutation
exister dans l'espace de noms global ou local (ce que le code ci-dessus essaie de faire) ou voulez-vous une recherche dynamique de noms lorsque vous effectuez un accès par point sur un module? C'est deux choses différentes.__getattr__
sur les modules est pris en charge à partir de Python 3.7Réponses:
Il y a quelque temps, Guido a déclaré que toutes les recherches de méthodes spéciales sur les classes de nouveau style contournaient
__getattr__
et__getattribute__
. Les méthodes Dunder avaient précédemment travaillé sur des modules - vous pouviez, par exemple, utiliser un module comme gestionnaire de contexte simplement en définissant__enter__
et__exit__
, avant que ces astuces ne se cassent .Récemment, certaines fonctionnalités historiques ont fait leur retour, le module
__getattr__
parmi eux, et donc le hack existant (un module se remplaçant par une classesys.modules
au moment de l'importation) ne devrait plus être nécessaire.Dans Python 3.7+, vous utilisez simplement la seule manière évidente. Pour personnaliser l'accès aux attributs sur un module, définissez une
__getattr__
fonction au niveau du module qui doit accepter un argument (nom de l'attribut), et renvoyer la valeur calculée ou lever unAttributeError
:Cela permettra également de se connecter aux importations "from", c'est-à-dire que vous pouvez renvoyer des objets générés dynamiquement pour des instructions telles que
from my_module import whatever
.Sur une note connexe, avec le module getattr, vous pouvez également définir une
__dir__
fonction au niveau du module à laquelle répondredir(my_module)
. Voir PEP 562 pour plus de détails.la source
m = ModuleType("mod")
et définissezm.__getattr__ = lambda attr: return "foo"
; cependant, quand je coursfrom mod import foo
, je reçoisTypeError: 'module' object is not iterable
.m.__getattr__ = lambda attr: "foo"
, plus vous devez définir une entrée pour le module avecsys.modules['mod'] = m
. Ensuite, il n'y a pas d'erreur avecfrom mod import foo
.my_module.whatever
pour l'invoquer (après unimport my_module
).Il y a deux problèmes de base que vous rencontrez ici:
__xxx__
les méthodes ne sont recherchées que sur la classeTypeError: can't set attributes of built-in/extension type 'module'
(1) signifie que toute solution devrait également garder une trace du module en cours d'examen, sinon chaque module aurait alors le comportement de substitution d'instance; et (2) signifie que (1) n'est même pas possible ... du moins pas directement.
Heureusement, sys.modules n'est pas pointilleux sur ce qui s'y passe, donc un wrapper fonctionnera, mais uniquement pour l'accès au module (c'est-à-dire
import somemodule; somemodule.salutation('world')
; pour l'accès au même module, vous devez à peu près extraire les méthodes de la classe de substitution et les ajouter àglobals()
eiher avec un méthode personnalisée sur la classe (j'aime utiliser.export()
) ou avec une fonction générique (comme celles déjà répertoriées comme réponses). Une chose à garder à l'esprit: si le wrapper crée une nouvelle instance à chaque fois, et que la solution globale ne l'est pas, vous vous retrouvez avec un comportement subtilement différent Oh, et vous ne pouvez pas utiliser les deux en même temps - c'est l'un ou l'autre.Mettre à jour
De Guido van Rossum :
Donc, la façon établie d'accomplir ce que vous voulez est de créer une seule classe dans votre module, et comme dernier acte du module, remplacez-la
sys.modules[__name__]
par une instance de votre classe - et maintenant vous pouvez jouer avec__getattr__
/__setattr__
/__getattribute__
si nécessaire.Remarque 1 : Si vous utilisez cette fonctionnalité, tout le reste du module, comme les globaux, les autres fonctions, etc., sera perdu lors de l'
sys.modules
affectation - assurez-vous donc que tout ce qui est nécessaire se trouve dans la classe de remplacement.Note 2 : Pour soutenir,
from module import *
vous devez avoir__all__
défini dans la classe; par exemple:Selon votre version de Python, il peut y avoir d'autres noms à omettre
__all__
. Leset()
peut être omis si la compatibilité Python 2 n'est pas nécessaire.la source
import sys
donnerNone
poursys
. Je suppose que ce hack n'est pas autorisé en Python 2.C'est un hack, mais vous pouvez envelopper le module avec une classe:
la source
import *
.Nous ne le faisons généralement pas de cette façon.
Voici ce que nous faisons.
Pourquoi? Pour que l'instance globale implicite soit visible.
Pour des exemples, regardez le
random
module, qui crée une instance globale implicite pour simplifier légèrement les cas d'utilisation où vous voulez un générateur de nombres aléatoires "simple".la source
Semblable à ce que @ Håvard S a proposé, dans un cas où je devais implémenter un peu de magie sur un module (comme
__getattr__
), je définirais une nouvelle classe qui héritetypes.ModuleType
et la mettraitsys.modules
(en remplaçant probablement le module où ma coutume aModuleType
été définie).Voir le
__init__.py
fichier principal de Werkzeug pour une implémentation assez robuste de ceci.la source
C'est hackish, mais ...
Cela fonctionne en itérant sur tous les objets de l'espace de noms global. Si l'élément est une classe, il itère sur les attributs de classe. Si l'attribut est appelable, il l'ajoute à l'espace de noms global en tant que fonction.
Il ignore tous les attributs qui contiennent "__".
Je n'utiliserais pas cela dans le code de production, mais cela devrait vous aider à démarrer.
la source
AddGlobalAttribute()
lorsqu'il y a un niveau de moduleAttributeError
- un peu l'inverse de la logique de @ Håvard S. Si j'ai une chance, je vais tester ceci et ajouter ma propre réponse hybride même si l'OP a déjà accepté cette réponse.NameError
exceptions pour l'espace de noms global (module) - ce qui explique pourquoi cette réponse ajoute des appelables pour chaque possibilité qu'elle trouve à l'espace de noms global actuel pour couvrir tous les cas possibles à l'avance.Voici ma propre contribution humble - un léger embellissement de la réponse très appréciée de @ Håvard S, mais un peu plus explicite (donc cela pourrait être acceptable pour @ S.Lott, même si probablement pas assez bien pour l'OP):
la source
Créez votre fichier de module contenant vos classes. Importez le module. Exécutez
getattr
sur le module que vous venez d'importer. Vous pouvez effectuer une importation dynamique en utilisant__import__
et extraire le module de sys.modules.Voici votre module
some_module.py
:Et dans un autre module:
Faire cela dynamiquement:
la source