Czy w Pythonie 2.5 jest sposób na stworzenie dekoratora, który zdobi klasę? W szczególności chcę użyć dekoratora, aby dodać element członkowski do klasy i zmienić konstruktora, aby przyjmował wartość dla tego elementu.
Szukam czegoś podobnego do następującego (który ma błąd składni w „klasie Foo:”:
def getId(self): return self.__id
class addID(original_class):
def __init__(self, id, *args, **kws):
self.__id = id
self.getId = getId
original_class.__init__(self, *args, **kws)
@addID
class Foo:
def __init__(self, value1):
self.value1 = value1
if __name__ == '__main__':
foo1 = Foo(5,1)
print foo1.value1, foo1.getId()
foo2 = Foo(15,2)
print foo2.value1, foo2.getId()
Myślę, że naprawdę szukam sposobu na zrobienie czegoś w rodzaju interfejsu C # w Pythonie. Przypuszczam, że muszę zmienić mój paradygmat.
źródło
Oprócz pytania, czy klasowe dekoratory są właściwym rozwiązaniem Twojego problemu:
W Pythonie 2.6 i nowszych istnieją dekoratory klas z @ -syntax, więc możesz napisać:
@addID class Foo: pass
W starszych wersjach możesz to zrobić w inny sposób:
class Foo: pass Foo = addID(Foo)
Zauważ jednak, że działa to tak samo, jak w przypadku dekoratorów funkcji i że dekorator powinien zwrócić nową (lub zmodyfikowaną oryginalną) klasę, co nie jest tym, co robisz w przykładzie. Dekorator addID wyglądałby tak:
def addID(original_class): orig_init = original_class.__init__ # Make copy of original __init__, so we can call it without recursion def __init__(self, id, *args, **kws): self.__id = id self.getId = getId orig_init(self, *args, **kws) # Call the original __init__ original_class.__init__ = __init__ # Set the class' __init__ to the new one return original_class
Możesz wtedy użyć odpowiedniej składni dla swojej wersji Pythona, jak opisano powyżej.
Ale zgadzam się z innymi, że dziedziczenie jest lepsze, jeśli chcesz zastąpić
__init__
.źródło
Nikt nie wyjaśnił, że możesz dynamicznie definiować klasy. Możesz więc mieć dekorator, który definiuje (i zwraca) podklasę:
def addId(cls): class AddId(cls): def __init__(self, id, *args, **kargs): super(AddId, self).__init__(*args, **kargs) self.__id = id def getId(self): return self.__id return AddId
Którego można użyć w Pythonie 2 (komentarz Blckknght, który wyjaśnia, dlaczego powinieneś kontynuować to w 2.6+) w następujący sposób:
class Foo: pass FooId = addId(Foo)
A w Pythonie 3 takim jak ten (ale uważaj, aby używać
super()
w swoich klasach):@addId class Foo: pass
Możesz więc mieć swoje ciasto i je zjeść - spadek i dekoratorzy!
źródło
super
wywołania w oryginalnej klasie. GdybyFoo
metoda o nazwie,foo
którasuper(Foo, self).foo()
ją wywołała, powtarzałaby się w nieskończoność, ponieważ nazwaFoo
jest powiązana z podklasą zwracaną przez dekorator, a nie z klasą oryginalną (do której żadna nazwa nie jest dostępna). Python 3 bez argumentówsuper()
unika tego problemu (zakładam, że za pomocą tej samej magii kompilatora, która pozwala mu w ogóle działać). Możesz również obejść ten problem, ręcznie dekorując klasę pod inną nazwą (tak jak w przykładzie z Pythonem 2.5).To nie jest dobra praktyka i dlatego nie ma takiego mechanizmu. Właściwym sposobem na osiągnięcie tego, czego chcesz, jest dziedziczenie.
Zapoznaj się z dokumentacją zajęć .
Mały przykład:
class Employee(object): def __init__(self, age, sex, siblings=0): self.age = age self.sex = sex self.siblings = siblings def born_on(self): today = datetime.date.today() return today - datetime.timedelta(days=self.age*365) class Boss(Employee): def __init__(self, age, sex, siblings=0, bonus=0): self.bonus = bonus Employee.__init__(self, age, sex, siblings)
W ten sposób Boss ma wszystko
Employee
, co ma, również własną__init__
metodę i własnych członków.źródło
Zgadzam się, że dziedziczenie lepiej pasuje do postawionego problemu.
Uważam, że to pytanie jest bardzo przydatne na zajęciach dekorowania, dzięki wszystkim.
Oto kilka innych przykładów, opartych na innych odpowiedziach, w tym jak dziedziczenie wpływa na rzeczy w Pythonie 2.7 (i @wraps , który zachowuje oryginalną dokumentację funkcji itp.):
def dec(klass): old_foo = klass.foo @wraps(klass.foo) def decorated_foo(self, *args ,**kwargs): print('@decorator pre %s' % msg) old_foo(self, *args, **kwargs) print('@decorator post %s' % msg) klass.foo = decorated_foo return klass @dec # No parentheses class Foo...
Często chcesz dodać parametry do swojego dekoratora:
from functools import wraps def dec(msg='default'): def decorator(klass): old_foo = klass.foo @wraps(klass.foo) def decorated_foo(self, *args ,**kwargs): print('@decorator pre %s' % msg) old_foo(self, *args, **kwargs) print('@decorator post %s' % msg) klass.foo = decorated_foo return klass return decorator @dec('foo decorator') # You must add parentheses now, even if they're empty class Foo(object): def foo(self, *args, **kwargs): print('foo.foo()') @dec('subfoo decorator') class SubFoo(Foo): def foo(self, *args, **kwargs): print('subfoo.foo() pre') super(SubFoo, self).foo(*args, **kwargs) print('subfoo.foo() post') @dec('subsubfoo decorator') class SubSubFoo(SubFoo): def foo(self, *args, **kwargs): print('subsubfoo.foo() pre') super(SubSubFoo, self).foo(*args, **kwargs) print('subsubfoo.foo() post') SubSubFoo().foo()
Wyjścia:
@decorator pre subsubfoo decorator subsubfoo.foo() pre @decorator pre subfoo decorator subfoo.foo() pre @decorator pre foo decorator foo.foo() @decorator post foo decorator subfoo.foo() post @decorator post subfoo decorator subsubfoo.foo() post @decorator post subsubfoo decorator
Użyłem dekoratora funkcji, ponieważ uważam je za bardziej zwięzłe. Oto klasa do dekoracji klasy:
class Dec(object): def __init__(self, msg): self.msg = msg def __call__(self, klass): old_foo = klass.foo msg = self.msg def decorated_foo(self, *args, **kwargs): print('@decorator pre %s' % msg) old_foo(self, *args, **kwargs) print('@decorator post %s' % msg) klass.foo = decorated_foo return klass
Bardziej niezawodna wersja, która sprawdza te nawiasy i działa, jeśli metody nie istnieją w ozdobionej klasie:
from inspect import isclass def decorate_if(condition, decorator): return decorator if condition else lambda x: x def dec(msg): # Only use if your decorator's first parameter is never a class assert not isclass(msg) def decorator(klass): old_foo = getattr(klass, 'foo', None) @decorate_if(old_foo, wraps(klass.foo)) def decorated_foo(self, *args ,**kwargs): print('@decorator pre %s' % msg) if callable(old_foo): old_foo(self, *args, **kwargs) print('@decorator post %s' % msg) klass.foo = decorated_foo return klass return decorator
Do
assert
sprawdza, czy dekorator nie został wykorzystany bez nawiasów. Jeśli tak, to dekorowana klasa jest przekazywana domsg
parametru dekoratora, który podnosi wartośćAssertionError
.@decorate_if
stosuje tylkodecorator
ifcondition
szacuje doTrue
.Metody
getattr
,callable
test i@decorate_if
są używane, aby dekorator nie zepsuł się, jeślifoo()
metoda nie istnieje w dekorowanej klasie.źródło
W rzeczywistości jest tutaj całkiem dobra implementacja dekoratora klas:
https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py
Właściwie uważam, że to całkiem ciekawa realizacja. Ponieważ jest podklasą klasy, którą ozdabia, będzie zachowywał się dokładnie tak, jak ta klasa, na przykład w postaci
isinstance
czeków.Ma dodatkową zaletę: nierzadko zdarza się, że
__init__
instrukcja w niestandardowym formularzu django wprowadza modyfikacje lub dodatki,self.fields
więc lepiej jest, aby zmianyself.fields
nastąpiły po wykonaniu wszystkich czynności__init__
dla danej klasy.Bardzo mądry.
Jednak w swojej klasie chcesz, aby dekoracja zmieniała konstruktora, co nie wydaje mi się dobrym przypadkiem użycia dla dekoratora klas.
źródło
Oto przykład, który odpowiada na pytanie o zwracanie parametrów klasy. Co więcej, nadal szanuje łańcuch dziedziczenia, tj. Zwracane są tylko parametry samej klasy. Funkcja
get_params
jest dodana jako prosty przykład, ale inne funkcjonalności można dodać dzięki modułowi inspect.import inspect class Parent: @classmethod def get_params(my_class): return list(inspect.signature(my_class).parameters.keys()) class OtherParent: def __init__(self, a, b, c): pass class Child(Parent, OtherParent): def __init__(self, x, y, z): pass print(Child.get_params()) >>['x', 'y', 'z']
źródło
Django ma
method_decorator
dekorator, który zamienia dowolnego dekoratora w dekorator metody, możesz zobaczyć, jak jest zaimplementowany wdjango.utils.decorators
:https://github.com/django/django/blob/50cf183d219face91822c75fa0a15fe2fe3cb32d/django/utils/decorators.py#L53
https://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class
źródło