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
Était-ce utile?

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:

  1. méthodes de __xxx__ ne sont regardé sur la classe
  2. 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']()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top