2009-03-25 16 views
98

W Pythonie 2.5, czy istnieje sposób na stworzenie dekoratora, który zdobi klasę? W szczególności chcę użyć dekoratora, aby dodać członka do klasy i zmienić konstruktor, aby uzyskać wartość dla tego elementu.Python Class Decorator

Szukasz czegoś jak poniżej (który ma błąd składni w 'klasy 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() 

Dzięki, Rob

Edit: Rereading to pytanie, myślę, że to, co jestem naprawdę po to sposób zrobić coś takiego interfejsu C# w Pythonie. muszę przełączyć mój paradygmat sądzę.

+34

Chociaż jest to użyteczne, dekoratory Python różnią się od wzoru dekoratora. Mam niefortunne imię. Mogę jednak skończyć w ten sposób. Dzięki. –

+13

@Robert Gowland: Bardzo dyplomatyczny. –

Odpowiedz

65

Pozwoliłbym, aby porzucić pojęcie, że można rozważyć podklasy zamiast podejścia, które zostały opisane. Jednak nie znając konkretny scenariusz, YMMV :-)

Co myślisz o to metaklasa. Funkcja __new__ w metaclass jest przekazywana pełną proponowaną definicją klasy, którą może następnie przepisać przed utworzeniem klasy. W tym czasie możesz podciąć konstruktora na nowy.

Przykład:

def substitute_init(self, id, *args, **kwargs): 
    pass 

class FooMeta(type): 

    def __new__(cls, name, bases, attrs): 
     attrs['__init__'] = substitute_init 
     return super(FooMeta, cls).__new__(cls, name, bases, attrs) 

class Foo(object): 

    __metaclass__ = FooMeta 

    def __init__(self, value1): 
     pass 

Wymiana konstruktora jest chyba nieco dramatyczna, ale język ma zapewnić wsparcie dla tego rodzaju głębokiej introspekcji i dynamicznej modyfikacji.

+0

Dziękuję, tego właśnie szukam. Klasa, która może modyfikować dowolną liczbę innych klas tak, że wszystkie mają określonego członka. Moje powody, dla których klasy nie dziedziczą ze wspólnej klasy ID, to to, że chcę mieć wersje innych niż identyfikatory klas, a także wersje ID. –

+0

Metaclasses były sposobem na zrobienie tego w Python2.5 lub starszym, ale obecnie bardzo często można używać dekoratorów klas (patrz odpowiedź Stevena), które są znacznie prostsze. –

11

to nie jest dobra praktyka i nie ma mec hanizm, aby to zrobić z tego powodu. Właściwym sposobem na osiągnięcie tego, co chcesz, jest dziedziczenie.

Zajrzyj do class documentation.

Trochę 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) 

ten sposób szef ma wszystko Employee ma, także z własnymi __init__ metody i własnych członków.

+3

Zgaduję, że chciałem mieć agnostyk Bossa wobec klasy, którą zawiera. Oznacza to, że mogą istnieć dziesiątki różnych klas, do których chcę zastosować funkcje Bossa. Czy pozostało mi posiadanie tych dwunastu klas po Bossie? –

+4

@Robert Gowland: Właśnie dlatego Python ma wiele dziedziczenia. Tak, powinieneś dziedziczyć różne aspekty z różnych klas rodziców. –

+7

@ S.Lott: Generalnie wielokrotne dziedziczenie jest złym pomysłem, nawet zbyt wysoki poziom dziedziczenia jest zły. Polecę ci trzymać się z dala od dziedziczenia wielokrotnego. – mpeterson

176

Niezależnie od tego, czy dekoratorzy klasy są właściwe rozwiązanie problemu:

w Pythonie 2.6 i wyżej, istnieje klasa dekoratorów z @ -syntax, więc można napisać:

@addID 
class Foo: 
    pass 

w starszych wersjach, można zrobić to w inny sposób:

class Foo: 
    pass 

Foo = addID(Foo) 

należy jednak pamiętać, że to działa tak samo, jak dla dekoratorów funkcyjnych i że dekorator powinien R eturn nowa (lub zmodyfikowana oryginalna) klasa, co nie jest tym, co robisz w tym przykładzie. AddID dekorator będzie wyglądać następująco:

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 

Następnie można użyć odpowiedniej składni dla twojej wersji Pythona, jak opisano powyżej.

Ale zgadzam się z innymi, że dziedziczenie jest lepiej nadaje się, jeśli chcesz nadpisać __init__.

+2

... mimo że w pytaniu wyraźnie wspomniano o Pythonie 2.5 :-) –

+0

Twój przykład powoduje błąd RuntimeError: maksymalna głębokość rekursji została przekroczona, ponieważ kiedy klasa jest tworzona, klasa original_class .__ init__ jest funkcją, która wywołuje samą __init__. W następnym komentarzu opublikuję działający przykład. –

+5

przepraszam za bałagan linesep, ale kod próbki nie są ściśle świetnie w komentarzach ...: def addID (original_class): original_class .__ orig__init__ = original_class .__ init__ def __init __ (self, * args, ** KWS): print "dekorator" self.id = 9 original_class .__ orig__init __ (self, * args, ** KWS) original_class .__ init__ = __init__ powrót original_class @addID class Foo: def __init __ (self): wydrukuj "Foo" a = Foo() drukuj a.id –

4

Jest rzeczywiście bardzo dobra implementacja klasy dekoratora tutaj:

https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py

I rzeczywiście, że jest to dość ciekawe wdrożenie. Ponieważ podklasy klasy, którą zdobi, zachowa się dokładnie tak jak ta klasa w takich sprawdzeniach, jak isinstance.

Posiada dodatkowe korzyści: to nie jest rzadkością w przypadku oświadczenie w niestandardowej formie django __init__ dokonać zmian lub uzupełnień do self.fields więc lepiej dla zmian self.fields się zdarzyć po wszystkim z __init__ prowadzi do klasy w pytanie.

Bardzo sprytny.

Jednak w twojej klasie naprawdę chcesz, aby dekoracja zmieniła konstruktora, co nie jest dobrym przykładem dla dekoratora klasy.

10

Nikt nie wyjaśnił, że można dynamicznie definiować klasy. więc można mieć dekorator definiujący (i powrót) podklasy:

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óre mogą być używane w Pythonie 2 (komentarz z Blckknght co wyjaśnia, dlaczego należy kontynuować to zrobić w 2.6+) tak:

class Foo: 
    pass 

FooId = addId(Foo) 

w Pythonie 3 tak (ale należy uważać, aby używać super() w klasach):

@addId 
class Foo: 
    pass 

więc można mieć ciastko i zjedz to - dziedziczenie i dekoratorów!

+2

Tworzenie podklas w dekoratorze jest niebezpieczne w Pythonie 2.6+, ponieważ łamie połączenia 'super' w oryginalnej klasie. Jeśli 'Foo' ma metodę o nazwie' foo', która nazywa się 'super (Foo, self) .foo()', to będzie rekurencyjne w nieskończoność, ponieważ nazwa 'Foo' jest powiązana z podklasą zwróconą przez dekorator, a nie z klasą oryginalną (która nie jest dostępna pod żadną nazwą). Pseudonowersja 'super() Pythona 3 omija ten problem (zakładam, że dzięki tej samej magii kompilatora, która pozwala mu działać w ogóle). Możesz również obejść problem, ręcznie dekorując klasę pod inną nazwą (tak jak w przykładzie Python 2.5). – Blckknght

+1

huh. dzięki, nie miałem pojęcia (używam Pythona 3). doda komentarz. –

0

Zgodzę się, że dziedziczenie lepiej pasuje do postawionego problemu.

Znalazłem to pytanie bardzo przydatne przy dekorowaniu klas, dziękuję wszystkim. Oto kolejne przykłady para, na podstawie innych odpowiedzi, takich jak dziedziczenie wpływa rzeczy w Pythonie 2.7 (i @wraps, który utrzymuje docstring pierwotnej funkcji jest, etc):

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 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 dekorator funkcji, jak je znaleźć bardziej zwięzłe. Oto klasa ozdobić klasę:

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 wytrzymała wersja, która sprawdza tych nawiasach i działa, czy metody nie istnieją na zdobione klasy:

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 

The assert kontrole, które dekorator nie był używany bez nawiasów.Jeśli tak, to dekorowana klasa jest przekazywana do parametru dekoratora, który podnosi wartość AssertionError.

@decorate_if dotyczy tylko decorator, jeśli condition ma wartość .

getattr, callable testy, a @decorate_if są wykorzystywane tak, że dekorator nie łamie jeśli metoda foo() nie istnieje na klasę czym zdobione.

Powiązane problemy