web-dev-qa-db-de.com

__getattr__ auf einem Modul

Wie kann das Äquivalent eines __getattr__ in einer Klasse, in einem Modul?

Beispiel

Beim Aufrufen einer Funktion, die in den statisch definierten Attributen eines Moduls nicht vorhanden ist, möchte ich eine Instanz einer Klasse in diesem Modul erstellen und die Methode mit demselben Namen aufrufen, der bei der Attributsuche im Modul fehlgeschlagen ist.

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 __== "__main__":
    # i hope here to have my __getattr__ function above invoked, since
    # salutation does not exist in the current namespace
    salutation("world")

Welches gibt:

[email protected]:~/Desktop$ python getattrmod.py 
Traceback (most recent call last):
  File "getattrmod.py", line 9, in <module>
    salutation("world")
NameError: name 'salutation' is not defined
111
Matt Joiner

Vor einiger Zeit erklärte Guido, dass alle speziellen Methodensuchen für Klassen neuen Stils __getattr__ Und __getattribute__ Umgehen . Dunder-Methoden hatten zuvor bei Modulen funktioniert - Sie können beispielsweise ein Modul als Kontextmanager verwenden, indem Sie einfach __enter__ Und __exit__ Vor diesen Tricks definieren pleite .

In letzter Zeit haben einige historische Funktionen ein Comeback erlebt, darunter das Modul __getattr__. Daher sollte der vorhandene Hack (ein Modul, das sich beim Import durch eine Klasse in sys.modules Ersetzt) ​​nicht mehr erforderlich sein.

In Python 3.7+ verwenden Sie nur den einen offensichtlichen Weg. Um den Attributzugriff auf ein Modul anzupassen, definieren Sie eine __getattr__ - Funktion auf Modulebene, die ein Argument (Name) akzeptieren soll of attribute), und geben Sie den berechneten Wert zurück oder erhöhen Sie ein AttributeError:

# my_module.py

def __getattr__(name: str) -> Any:
    ...

Dies ermöglicht auch das Einhängen von "from" -Importen, d. H. Sie können dynamisch generierte Objekte für Anweisungen wie from my_module import whatever Zurückgeben.

In einem verwandten Hinweis können Sie zusammen mit dem Modul getattr auch eine __dir__ - Funktion auf Modulebene definieren, um auf dir(my_module) zu reagieren. Siehe PEP 562 für Details.

24
wim

Es gibt zwei grundlegende Probleme, auf die Sie hier stoßen:

  1. __xxx__-Methoden werden nur in der Klasse nachgeschlagen
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) bedeutet, dass jede Lösung auch verfolgen müsste, welches Modul untersucht wird, da sonst alle Module das Instancesubstitutionsverhalten aufweisen würden; und (2) bedeutet, dass (1) nicht einmal möglich ist ... zumindest nicht direkt.

Glücklicherweise ist sys.modules nicht wählerisch, was dahinter steckt, so dass ein Wrapper funktioniert, sondern nur für den Modulzugriff (dh import somemodule; somemodule.salutation('world'); für den Zugriff auf dasselbe Modul müssen Sie die Methoden aus der Substitutionsklasse so ziemlich wegreißen und füge sie entweder mit einer benutzerdefinierten Methode für die Klasse (ich verwende gerne globals()) oder mit einer generischen Funktion (wie den bereits als Antworten aufgelisteten) zu .export() hinzu Denken Sie daran: Wenn der Wrapper jedes Mal eine neue Instanz erstellt und die globale Lösung dies nicht ist, führt dies zu einem geringfügig anderen Verhalten andere.


Update

Von Guido van Rossum :

Es gibt tatsächlich einen Hack, der gelegentlich verwendet und empfohlen wird: Ein Modul kann eine Klasse mit der gewünschten Funktionalität definieren und sich am Ende in sys.modules durch eine Instanz dieser Klasse (oder durch die Klasse, wenn Sie darauf bestehen) ersetzen , aber das ist im Allgemeinen weniger nützlich). Z.B.:

# module foo.py

import sys

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>

sys.modules[__name__] = Foo()

Dies funktioniert, weil die Import-Maschinerie diesen Hack aktiv aktiviert und als letzter Schritt das eigentliche Modul nach dem Laden aus sys.modules zieht. (Dies ist kein Zufall. Der Hack wurde vor langer Zeit vorgeschlagen und wir entschieden, dass wir genug davon mochten, um ihn in den Importmaschinen zu unterstützen.)

Um das zu erreichen, müssen Sie eine einzelne Klasse in Ihrem Modul erstellen und als letzten Schritt des Moduls sys.modules[__name__] durch eine Instanz Ihrer Klasse ersetzen. Jetzt können Sie nach Bedarf mit __getattr__/__setattr__/__getattribute__ spielen .

Beachten Sie, dass bei Verwendung dieser Funktion alle anderen Elemente des Moduls, z. B. globale Elemente, andere Funktionen usw., verloren gehen, wenn die sys.modules-Zuweisung vorgenommen wird. Stellen Sie daher sicher, dass sich alle erforderlichen Elemente in der Ersetzungsklasse befinden.

107
Ethan Furman

Dies ist ein Hack, aber Sie können das Modul mit einer Klasse abschließen:

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__])
45
Håvard S

Das machen wir normalerweise nicht so.

Was wir tun, ist das.

class A(object):
....

# The implicit global instance
a= A()

def salutation( *arg, **kw ):
    a.salutation( *arg, **kw )

Warum? Damit ist die implizite globale Instanz sichtbar.

Schauen Sie sich zum Beispiel das random -Modul an, das eine implizite globale Instanz erstellt, um die Anwendungsfälle, in denen Sie einen "einfachen" Zufallszahlengenerator wünschen, etwas zu vereinfachen.

19
S.Lott

Ähnlich dem, was @ Håvard S vorschlug, würde ich in einem Fall, in dem ich etwas Magie in einem Modul implementieren musste (wie __getattr__), Eine neue Klasse definieren, die von types.ModuleType Erbt, und diese einfügen sys.modules (Ersetzt wahrscheinlich das Modul, in dem mein benutzerdefiniertes ModuleType definiert wurde).

Siehe die Hauptdatei __init__.py von Werkzeug für eine ziemlich robuste Implementierung davon.

13
Matt Anderson

Das ist hackisch, aber ...

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 __== "__main__":
    salutation("world")
    farewell("goodbye", "world")

Dies funktioniert, indem alle Objekte im globalen Namespace durchlaufen werden. Wenn es sich bei dem Element um eine Klasse handelt, werden die Klassenattribute durchlaufen. Wenn das Attribut aufrufbar ist, wird es als Funktion zum globalen Namespace hinzugefügt.

Es ignoriert alle Attribute, die "__" enthalten.

Ich würde dies nicht in Produktionscode verwenden, aber es sollte Ihnen den Einstieg erleichtern.

7
grieve

Hier ist mein bescheidener Beitrag - eine leichte Verschönerung der hoch bewerteten Antwort von @ Håvard S, aber etwas expliziter (daher könnte es für @ S.Lott akzeptabel sein, auch wenn es für das OP wahrscheinlich nicht gut genug ist):

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 __== "__main__":
    _globals.salutation("world")
4
martineau