Wie kann das Äquivalent von a __getattr__
in einer Klasse, in einem Modul implementiert werden?
Beispiel
Wenn ich eine Funktion aufrufe, 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 __name__ == "__main__":
# i hope here to have my __getattr__ function above invoked, since
# salutation does not exist in the current namespace
salutation("world")
Welches gibt:
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
quelle
quelle
salutation
im globalen oder lokalen Namespace existieren (wie im obigen Code versucht) oder möchten Sie eine dynamische Suche nach Namen, wenn Sie einen Punktzugriff auf ein Modul vornehmen? Es sind zwei verschiedene Dinge.__getattr__
on modules wird von Python 3.7Antworten:
Vor einiger Zeit erklärte Guido, dass alle speziellen Methodensuchen für Klassen neuen Stils Bypass
__getattr__
und umgehen__getattribute__
. Dunder-Methoden hatten zuvor an Modulen gearbeitet - Sie konnten beispielsweise ein Modul als Kontextmanager verwenden, indem Sie einfach definieren__enter__
und__exit__
bevor diese Tricks brachen .In letzter Zeit haben einige historische Features ein Comeback erlebt,
__getattr__
darunter auch das Modul. Daher sollte der vorhandene Hack (ein Modul, das sichsys.modules
beim Import durch eine Klasse 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 des Attributs) akzeptieren soll, und geben Sie den berechneten Wert zurück oder lösen Sie Folgendes ausAttributeError
:Dies ermöglicht auch Hooks in "von" -Importen, dh Sie können dynamisch generierte Objekte für Anweisungen wie zurückgeben
from my_module import whatever
.In einem verwandten Hinweis können Sie zusammen mit dem Modul getattr auch eine
__dir__
Funktion auf Modulebene definieren, auf die reagiert werden solldir(my_module)
. Siehe PEP 562 für Details.quelle
m = ModuleType("mod")
und setzem.__getattr__ = lambda attr: return "foo"
; Wenn ich jedoch rennefrom mod import foo
, bekomme ichTypeError: 'module' object is not iterable
.m.__getattr__ = lambda attr: "foo"
, und Sie müssen einen Eintrag für das Modul mit definierensys.modules['mod'] = m
. Danach gibt es keinen Fehler mitfrom mod import foo
.my_module.whatever
, um sie aufzurufen (nach einemimport my_module
).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 muss, welches Modul untersucht wird, andernfalls würde jedes Modul dann das Instanzersetzungsverhalten aufweisen; und (2) bedeutet, dass (1) nicht einmal möglich ist ... zumindest nicht direkt.
Glücklicherweise ist sys.modules nicht wählerisch, was dort vor sich geht, damit ein Wrapper funktioniert, sondern nur für
import somemodule; somemodule.salutation('world')
den Modulzugriff (dh für den Zugriff auf dasselbe Modul müssen Sie die Methoden aus der Substitutionsklasse ziehen und sieglobals()
mit einem zu einem hinzufügen Benutzerdefinierte Methode für die Klasse (die ich gerne verwende.export()
) oder mit einer generischen Funktion (wie die bereits als Antworten aufgeführten). Beachten Sie Folgendes: Wenn der Wrapper jedes Mal eine neue Instanz erstellt und die globale Lösung nicht. Sie haben am Ende ein subtil anderes Verhalten. Oh, und Sie können nicht beide gleichzeitig verwenden - es ist das eine oder andere.Aktualisieren
Von Guido van Rossum :
Der etablierte Weg, um das zu erreichen, was Sie wollen, besteht darin, eine einzelne Klasse in Ihrem Modul zu erstellen und als letzten Akt des Moduls durch
sys.modules[__name__]
eine Instanz Ihrer Klasse zu ersetzen - und jetzt können Sie nach Bedarf mit__getattr__
/__setattr__
/ spielen__getattribute__
.Hinweis 1 : Wenn Sie diese Funktionalität verwenden, gehen alle anderen Elemente des Moduls, wie z. B. Globals, andere Funktionen usw., bei der
sys.modules
Zuweisung verloren. Stellen Sie daher sicher, dass sich alle erforderlichen Elemente in der Ersatzklasse befinden .Hinweis 2 : Zur Unterstützung
from module import *
müssen Sie__all__
in der Klasse definiert haben . beispielsweise:Abhängig von Ihrer Python-Version können andere Namen weggelassen werden
__all__
. Diesset()
kann weggelassen werden, wenn keine Python 2-Kompatibilität erforderlich ist.quelle
import sys
GebenNone
fürsys
. Ich vermute, dieser Hack wird in Python 2 nicht sanktioniert.Dies ist ein Hack, aber Sie können das Modul mit einer Klasse umschließen:
quelle
import *
.Wir machen das normalerweise nicht so.
Was wir tun, ist dies.
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 benötigen, leicht zu vereinfachen.quelle
Ähnlich wie bei @ Håvard S vorgeschlagen, würde ich in einem Fall, in dem ich etwas Magie auf einem Modul (wie
__getattr__
) implementieren musste , eine neue Klasse definieren, die von dieser erbttypes.ModuleType
und diese einfügtsys.modules
(wahrscheinlich das Modul ersetzen, in dem meine GewohnheitModuleType
definiert wurde).Eine ziemlich robuste Implementierung finden Sie in der Hauptdatei
__init__.py
von Werkzeug .quelle
Das ist hackisch, aber ...
Dies funktioniert, indem alle Objekte im globalen Namespace durchlaufen werden. Wenn das Element eine Klasse ist, durchläuft es die Klassenattribute. Wenn das Attribut aufrufbar ist, wird es als Funktion zum globalen Namespace hinzugefügt.
Es werden alle Attribute ignoriert, die "__" enthalten.
Ich würde dies nicht im Produktionscode verwenden, aber es sollte Ihnen den Einstieg erleichtern.
quelle
AddGlobalAttribute()
wenn eine Modulebene vorhanden istAttributeError
- eine Art Umkehrung der Logik von @ Håvard S. Wenn ich eine Chance habe, werde ich dies testen und meine eigene hybride Antwort hinzufügen, obwohl das OP diese Antwort bereits akzeptiert hat.NameError
Ausnahmen für den globalen (Modul-) Namespace abzufangen - was erklärt, warum diese Antwort dem aktuellen globalen Namespace Callables für jede Möglichkeit hinzufügt, die er findet, um jeden möglichen Fall im Voraus abzudecken.Hier ist mein bescheidener Beitrag - eine leichte Verschönerung der hoch bewerteten Antwort von @ Håvard S, aber etwas expliziter (so könnte es für @ S.Lott akzeptabel sein, obwohl es für das OP wahrscheinlich nicht gut genug ist):
quelle
Erstellen Sie Ihre Moduldatei mit Ihren Klassen. Importieren Sie das Modul. Führen
getattr
Sie das gerade importierte Modul aus. Sie können einen dynamischen Import mit durchführen__import__
und das Modul aus sys.modules ziehen.Hier ist dein Modul
some_module.py
:Und in einem anderen Modul:
Dynamisch machen:
quelle