2010-06-25 17 views
21

Chcę zaimplementować decorator pattern w języku Python, i zastanawiałem się, czy istnieje sposób na napisanie dekoratora, który po prostu implementuje funkcję, którą chce zmodyfikować, bez pisania tablicy kotłowej dla wszystkich funkcji, które są właśnie przekazywane do udekorowanego obiektu. Tak:Wdrażanie wzoru dekoratora w Pythonie

class foo(object): 
    def f1(self): 
     print "original f1" 
    def f2(self): 
     print "original f2" 

class foo_decorator(object): 
    def __init__(self, decoratee): 
     self._decoratee = decoratee 
    def f1(self): 
     print "decorated f1" 
     self._decoratee.f1() 
    def f2(self):    # I would like to leave that part out 
     self._decoratee.f2() 

chciałbym mieć połączenia do foo_decorator.f2 przekazane decoratee.f2 automatycznie. Czy istnieje sposób napisania ogólnej metody, która przekazuje wszystkie niezatwierdzone wywołania funkcji do decoratee?

+1

Czy możesz podać przykład, w którym proste podklasy nie działa? Możesz też podklasować dynamicznie - ten wzorzec wydaje się być obejściem dla języków, które nie mogą tego zrobić lub nie obsługują wielu dziedzin. –

+0

Chcę udekorować obiekty w czasie wykonywania. Chcę zastosować różne dekoratory do obiektu i móc je ponownie usunąć. Podklasy nie można zmienić instancji po jej utworzeniu, czy też nie? –

+0

Jeśli masz klasę 'A' i zmienisz' A', tj. Dodanie nowej metody, 'A.foo = lambda self: self', to będzie odzwierciedlać wszystkie wystąpienia A .. ponieważ * wszystko * jest określane w czasie wykonywania. Świetny sposób na stworzenie absolutnie nieosiągalnego kodu. –

Odpowiedz

30

Można użyć __getattr__:

class foo(object): 
    def f1(self): 
     print "original f1" 
    def f2(self): 
     print "original f2" 

class foo_decorator(object): 
    def __init__(self, decoratee): 
     self._decoratee = decoratee 
    def f1(self): 
     print "decorated f1" 
     self._decoratee.f1() 
    def __getattr__(self, name): 
     return getattr(self._decoratee, name) 

u = foo() 
v = foo_decorator(u) 
v.f1() 
v.f2() 
+1

To świetny pomysł. Należy jednak zauważyć, że jeśli użyjesz abstrakcyjnych klas bazowych (tj. Importuj abc i użyjesz __metaclass__ = abc.ABCMeta do zdefiniowania abstrakcyjnych metod i właściwości), to nie zadziała. – abergou

+0

To nie działa w przypadku takich funkcji, jak __str__. Co się tam dzieje? –

+0

@JohnKitchin: Przekierowanie nie działa w przypadku metod podwójnego podkreślenia, ponieważ muszą one (ze względu na wydajność) zostać zdefiniowane w klasie, a nie w instancji. [(więcej informacji)] (https://docs.python.org/3/reference/datamodel.html#special-lookup) Sądzę, że jedynym rozwiązaniem jest jawne zdefiniowanie i przekierowanie metod takich jak '__str__'. –

2

To nie jest prawdopodobnie najlepsze praktyki, ale można rozszerzyć funkcjonalność przypadkach, jak mam zrobić, aby pomóc przejść mój kod z ORM Django do SQLAlachemy, co następuje:

def _save(self): 
    session.add(self) 
    session.commit() 
setattr(Base,'save',_save) 
+1

Rozważałem to również, ale to bardzo mi szkodzi. Może to dlatego, że pochodzę z C++? –

+2

Uczciwie się z tym czuć. Można to uznać za łatanie małpy. Jednak działa dobrze z małym kodem, to zupełnie inny świat z większymi możliwościami od C, gdy wszystko jest dynamiczne i przez odniesienie. – andyortlieb

+0

Podoba mi się. W tym przykładzie wszystko inne wprowadziłoby bardziej złożoną IMO. – Chomeh

1

Schemat UML w połączonym artykule w Wikipedii jest błędny, podobnie jak kod.

Jeśli podążysz za "wzorem dekoratora", klasa dekoratora wywodzi się z klasy zdobionej podstawą. (W diagramie UML brakuje strzałki dziedziczenia z WindowDecorator do Window).

z

class foo_decorator(foo): 

nie trzeba zaimplementować metody bez dekoracji.

BTW: W silnych językach maszynowych jest jeszcze jeden powód, dlaczego dekorator musi pochodzić z klas zdobionej: w przeciwnym razie nie byłbyś w stanie łączyć dekoratorów.

+0

Diagram UML przedstawia zarówno dziedziczenie, jak i agregację (dwie podstawowe części dla wzoru dekoratora) klasy bazowej.Odziedziczysz po klasie bazowej, więc wyglądasz jak oryginał i przechowujesz odwołanie do instancji klasy bazowej, gdzie możesz zaizolować dostęp do niej. Artykuł wikipedia mówi to w krokach 1 i 2: "(1) Podklasy oryginalnego komponentu, (2) dodaj wskaźnik komponentu jako pole". Tak więc pierwotne pytanie brzmi: pisanie w języku Python, a nie o dekorator, ani dekoratorów Python! – maxpolk

8

Jako uzupełnienie odpowiedzi Filipa; jeśli trzeba nie tylko zdobią, ale zachować typ obiektu, Python pozwala na podklasy instancji w czasie wykonywania:

class foo(object): 
    def f1(self): 
     print "original f1" 

    def f2(self): 
     print "original f2" 


class foo_decorator(object): 
    def __new__(cls, decoratee): 
     cls = type('decorated', 
        (foo_decorator, decoratee.__class__), 
        decoratee.__dict__) 
     return object.__new__(cls) 

    def f1(self): 
     print "decorated f1" 
     super(foo_decorator, self).f1() 


u = foo() 
v = foo_decorator(u) 
v.f1() 
v.f2() 
print 'isinstance(v, foo) ==', isinstance(v, foo) 

To jest trochę bardziej złożone niż to absolutnie niezbędne dla przykładu, gdzie wiedzieć, że klasa jest dekorowana z góry.

Ten potęga wystarczyć:

class foo_decorator(foo): 
    def __init__(self, decoratee): 
     self.__dict__.update(decoratee.__dict__) 

    def f1(self): 
     print "decorated f1" 
     super(foo_decorator, self).f1() 
0

celu uzupełnienia @Alec Thomas odpowiadanie. Zmodyfikując jego odpowiedź, podążałem za wzorem dekoratora. W ten sposób nie musisz znać klasy, którą dekorujesz z góry.

class Decorator(object): 
    def __new__(cls, decoratee): 
     cls = type('decorated', 
        (cls, decoratee.__class__), 
        decoratee.__dict__) 
     return object.__new__(cls) 

Następnie można go używać jako:

class SpecificDecorator(Decorator): 
    def f1(self): 
     print "decorated f1" 
     super(foo_decorator, self).f1() 

class Decorated(object): 
    def f1(self): 
     print "original f1" 


d = SpecificDecorator(Decorated()) 
d.f1() 
0

W jednym z moich projektów, musiałem też zrobić jedną konkretną rzecz, że jest to, że nawet podstawowa obiekt powinien właściwie wykonać metodę został ponownie zastosowany w dekoratorze. W rzeczywistości jest to dość łatwe, jeśli wiesz, gdzie celować.

Przypadek stosowanie jest:

  • mam obiektu X ze sposobami A i B.
  • Tworzę klasę dekoratora Y, która zastępuje A.
  • Jeśli zainicjuję Y (X) i zadzwonię A, użyje on udekorowanego A zgodnie z oczekiwaniami.
  • Jeśli B zadzwoni A, to jeśli zainicjuję Y (X) i wywołasz B na dekoratorze, wywołanie z wnętrza B następnie przechodzi do starego A na pierwotnym obiekcie, co było niepożądane. Chcę, żeby stary B również nazwał nowy A.

Jest możliwe, aby osiągnąć ten problem tak:

import inspect 
import six  # for handling 2-3 compatibility 

class MyBaseDecorator(object): 
    def __init__(self, decorated): 
     self.decorated = decorated 

    def __getattr__(self, attr): 
     value = getattr(self.decorated, attr) 
     if inspect.ismethod(value): 
      function = six.get_method_function(value) 
      value = function.__get__(self, type(self)) 
     return value 

class SomeObject(object): 
    def a(self): 
     pass 

    def b(self): 
     pass 

class MyDecorator(MyBaseDecorator): 
    def a(self): 
     pass 

decorated = MyDecorator(SomeObject()) 

ta może nie działać po wyjęciu z pudełka, jak Wpisałem wszystko inne oprócz metody getattr od wierzchołka głowy.

Kod wyszukuje żądany atrybut w dekorowanym obiekcie, a jeśli jest to metoda (nie działa teraz dla właściwości, ale zmiana w celu ich obsługi nie powinna być zbyt trudna), kod pobiera następnie rzeczywistą wartość funkcja poza metodą i za pomocą wywołania interfejsu deskryptora "rebinduje" funkcję jako metodę, ale na dekoratorze. Następnie jest zwracana i najprawdopodobniej wykonywana.

Efekt jest taki, że jeśli b kiedykolwiek wywoła a na oryginalnym obiekcie, wtedy gdy obiekt zostanie udekorowany i wywoływane jest dowolne wywołanie metody z dekoratora, dekorator upewnia się, że wszystkie metody dostępne są powiązane z obiektem. dekorator zamiast tego, dlatego przegląda rzeczy przy użyciu dekoratora, a nie oryginalnego obiektu, dlatego metody określone w dekoratorze mają pierwszeństwo.

P.S .: Tak, wiem, że wygląda prawie jak dziedzictwo, ale odbywa się to w sensie kompozycji wielu obiektów.