Wie kann das Äquivalent eines __getattr__
in einer Klasse, in einem Modul?
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
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.
Es gibt zwei grundlegende Probleme, auf die Sie hier stoßen:
__xxx__
-Methoden werden nur in der Klasse nachgeschlagenTypeError: 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.
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__])
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.
Ä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.
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.
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")