Jak można zaimplementować odpowiednik a __getattr__
w klasie, w module?
Przykład
Podczas wywoływania funkcji, która nie istnieje w statycznie zdefiniowanych atrybutach modułu, chcę utworzyć wystąpienie klasy w tym module i wywołać na niej metodę o tej samej nazwie, która nie powiodła się w wyszukiwaniu atrybutów w 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")
Co daje:
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
źródło
źródło
salutation
istnieć w globalnej lub lokalnej przestrzeni nazw (co próbuje zrobić powyższy kod), czy chcesz dynamicznego wyszukiwania nazw podczas wykonywania dostępu z kropką w module? To dwie różne rzeczy.__getattr__
na modułach jest obsługiwany od Pythona 3.7Odpowiedzi:
Jakiś czas temu Guido zadeklarował, że wszystkie wyszukiwania metod specjalnych w klasach nowego stylu pomijają
__getattr__
i__getattribute__
. Metody Dunder pracowały wcześniej na modułach - można na przykład użyć modułu jako menedżera kontekstu, po prostu definiując__enter__
i__exit__
zanim te sztuczki się zepsuły .Ostatnio niektóre funkcje historyczne powróciły, a
__getattr__
wśród nich moduł , więc istniejący hack (moduł zastępujący się klasą wsys.modules
czasie importu) nie powinien być już potrzebny.W Pythonie 3.7+ używasz tylko jednego oczywistego sposobu. Aby dostosować dostęp do atrybutów w module, zdefiniuj
__getattr__
funkcję na poziomie modułu, która powinna akceptować jeden argument (nazwę atrybutu) i zwrócić obliczoną wartość lub podnieśćAttributeError
:Pozwoli to również na przechwycenie do importu „z”, tj. Możesz zwracać dynamicznie generowane obiekty dla instrukcji, takich jak
from my_module import whatever
.W podobnej notatce, wraz z modułem getattr, możesz także zdefiniować
__dir__
funkcję na poziomie modułu, na którą ma odpowiadaćdir(my_module)
. Szczegóły w PEP 562 .źródło
m = ModuleType("mod")
i ustawięm.__getattr__ = lambda attr: return "foo"
; jednak kiedy biegnęfrom mod import foo
, dostajęTypeError: 'module' object is not iterable
.m.__getattr__ = lambda attr: "foo"
, dodatkowo musisz zdefiniować wpis dla modułu za pomocąsys.modules['mod'] = m
. Później nie ma błędu wfrom mod import foo
.my_module.whatever
celu wywołania (poimport my_module
).Występują tutaj dwa podstawowe problemy:
__xxx__
metody są wyszukiwane tylko w klasieTypeError: can't set attributes of built-in/extension type 'module'
(1) oznacza, że każde rozwiązanie musiałoby również śledzić, który moduł był badany, w przeciwnym razie każdy moduł miałby wówczas zachowanie polegające na zastępowaniu instancji; a (2) oznacza, że (1) nie jest nawet możliwe ... przynajmniej nie bezpośrednio.
Na szczęście sys.modules nie jest wybredny, jeśli chodzi o to, co tam jest, więc opakowanie będzie działać, ale tylko w przypadku dostępu do modułu (tj. Aby uzyskać dostęp
import somemodule; somemodule.salutation('world')
do tego samego modułu, musisz prawieglobals()
wyciągnąć metody z klasy podstawienia i dodać je do eiher za pomocą niestandardową metodę w klasie (lubię używać.export()
) lub z funkcją ogólną (taką jak te już wymienione jako odpowiedzi). Należy pamiętać o jednej rzeczy: jeśli opakowanie tworzy za każdym razem nową instancję, a rozwiązanie globalne nie, kończysz z subtelnie innym zachowaniem. Och, i nie możesz używać obu jednocześnie - to jedno lub drugie.Aktualizacja
Od Guido van Rossuma :
Tak więc ustalonym sposobem osiągnięcia tego, co chcesz, jest utworzenie pojedynczej klasy w swoim module i jako ostatni akt modułu zastąp
sys.modules[__name__]
go instancją swojej klasy - a teraz możesz grać z__getattr__
/__setattr__
/__getattribute__
w razie potrzeby.Uwaga 1 : Jeśli użyjesz tej funkcji, wszystko inne w module, takie jak globalne, inne funkcje itp. Zostanie utracone po
sys.modules
przypisaniu - więc upewnij się, że wszystko, co potrzebne, znajduje się w klasie zastępczej.Uwaga 2 : Aby wspierać
from module import *
, musisz__all__
zdefiniować w klasie; na przykład:W zależności od wersji Pythona mogą istnieć inne nazwy do pominięcia
__all__
.set()
Można pominąć, jeżeli Python 2 kompatybilność nie jest potrzebne.źródło
import sys
daćNone
nasys
. Zgaduję, że ten hack nie jest usankcjonowany w Pythonie 2.To hack, ale możesz opakować moduł klasą:
źródło
import *
.Zwykle nie robimy tego w ten sposób.
To, co robimy, to to.
Czemu? Aby niejawna instancja globalna była widoczna.
Na przykład spójrz na
random
moduł, który tworzy niejawną instancję globalną, aby nieco uprościć przypadki użycia, w których potrzebny jest „prosty” generator liczb losowych.źródło
Podobnie do tego, co zaproponował @ Håvard S, w przypadku, gdy potrzebowałem zaimplementować jakąś magię na module (np.
__getattr__
), Zdefiniowałbym nową klasę, która dziedziczytypes.ModuleType
i umieściłbym jąsys.modules
(prawdopodobnie zastępując moduł, w którymModuleType
zdefiniowano mój niestandardowy ).Zobacz główny
__init__.py
plik Werkzeug, aby uzyskać dość solidną implementację tego.źródło
To jest hacking, ale ...
Działa to poprzez iterację po wszystkich obiektach w globalnej przestrzeni nazw. Jeśli element jest klasą, iteruje po atrybutach klasy. Jeśli atrybut jest wywoływalny, dodaje go do globalnej przestrzeni nazw jako funkcję.
Ignoruje wszystkie atrybuty zawierające „__”.
Nie użyłbym tego w kodzie produkcyjnym, ale powinno ci to pomóc.
źródło
AddGlobalAttribute()
wtedy, gdy istnieje poziom modułuAttributeError
- coś w rodzaju odwrotności logiki @ Håvard S. Jeśli będę miał okazję, przetestuję to i dodam własną odpowiedź hybrydową, mimo że OP zaakceptował już tę odpowiedź.NameError
wyjątków dla globalnej (modułu) przestrzeni nazw jest bardzo trudne (nieskomplikowane?) - co wyjaśnia, dlaczego ta odpowiedź dodaje wywołania dla każdej możliwości, jaką znajdzie w bieżącej globalnej przestrzeni nazw, aby uwzględnić każdy możliwy przypadek z wyprzedzeniem.Oto mój własny skromny wkład - niewielkie upiększenie wysoko ocenianej odpowiedzi @ Håvard S, ale nieco bardziej wyraźne (więc może być do zaakceptowania dla @ S.Lott, nawet jeśli prawdopodobnie nie jest wystarczająco dobre dla OP):
źródło
Utwórz plik modułu zawierający Twoje klasy. Zaimportuj moduł. Uruchom
getattr
na module, który właśnie zaimportowałeś. Możesz wykonać dynamiczny import za pomocą__import__
i wyciągnąć moduł z sys.modules.Oto Twój moduł
some_module.py
:A w innym module:
Robiąc to dynamicznie:
źródło