2012-12-30 11 views
7

Mam hierarchię obiektów, w której prawie wszystkie metody są metod klasy. Wygląda to tak:Python - czy mogę programowo dekorować metody klasy z instancji klasy?

class ParentObject(object): 
    def __init__(self): 
     pass 

    @classmethod 
    def smile_warmly(cls, the_method): 
     def wrapper(kls, *args, **kwargs): 
      print "-smile_warmly - "+kls.__name__ 
      the_method(*args, **kwargs) 
     return wrapper 

    @classmethod 
    def greetings(cls): 
     print "greetings" 

class SonObject(ParentObject): 
    @classmethod 
    def hello_son(cls): 
     print "hello son" 

    @classmethod 
    def goodbye(cls): 
     print "goodbye son" 

class DaughterObject(ParentObject): 
    @classmethod 
    def hello_daughter(cls): 
     print "hello daughter" 

    @classmethod 
    def goodbye(cls): 
     print "goodbye daughter" 

if __name__ == '__main__': 
    son = SonObject() 
    son.greetings() 
    son.hello_son() 
    son.goodbye() 
    daughter = DaughterObject() 
    daughter.greetings() 
    daughter.hello_daughter() 
    daughter.goodbye() 

kodu jak podano wyjść następujące:

greetings 
hello son 
goodbye son 
greetings 
hello daughter 
goodbye daughter 

Chciałbym kod do wyjścia następuje:

-smile_warmly - SonObject 
greetings 
-smile_warmly - SonObject 
hello son 
-smile_warmly - SonObject 
goodbye son 
-smile_warmly - DaughterObject 
greetings 
-smile_warmly - DaughterObject 
hello daughter 
-smile_warmly - DaughterObject 
goodbye daughter 

ale nie chcę przed każdą metodą dodać linię @smile_warmly (a gdy spróbuję to zrobić w powyższym kodzie, otrzymuję komunikat o błędzie TypeError: 'classmethod' object is not callable). Przeciwnie, chciałbym, aby dekoracja każdej metody odbywała się programowo w metodzie __init__().

Czy można programowo dekorować metody w Pythonie?

EDIT: Znaleziono coś, co wydaje się działać - zobacz moją odpowiedź poniżej. Dzięki BrenBarn.

+1

Czy pojęcie metaclasses znajomo? –

+0

Wygląda na to, że będziesz chciał spojrzeć na to: http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python Być może, jeśli używasz dekoratorów również ten facet: http://stackoverflow.com/questions/739654/understanding-python-decorators –

+0

Słyszałem o nich, ale o to chodzi. Więc nie. – jononomo

Odpowiedz

14

Wszystko, co robi dekorator, to zwrócenie nowej funkcji. To:

@deco 
def foo(): 
    # blah 

jest taki sam jak ten:

def foo(): 
    # blah 
foo = deco(foo) 

Można zrobić to samo, kiedy tylko chcesz, bez składni @, po prostu poprzez zastąpienie funkcji z tym, co lubisz. Tak więc w __init__ lub gdziekolwiek indziej, można przeglądać wszystkie metody i dla każdego zastąpić je smilewarmly(meth).

Jednak zamiast robić to w __init__, bardziej sensowne byłoby robienie tego podczas tworzenia klasy. Można to zrobić za pomocą metaklasą, lub prościej z dekorator klasy:

def smileDeco(func): 
    def wrapped(*args, **kw): 
     print ":-)" 
     func(*args, **kw) 
    return classmethod(wrapped) 

def makeSmiley(cls): 
    for attr, val in cls.__dict__.iteritems(): 
     if callable(val) and not attr.startswith("__"): 
      setattr(cls, attr, smileDeco(val)) 
    return cls 

@makeSmiley 
class Foo(object): 
    def sayStuff(self): 
     print "Blah blah" 

>>> Foo().sayStuff() 
:-) 
Blah blah 

W tym przykładzie mogę umieścić dekorację classmethod wewnątrz mojego smileDeco dekoratora. Można również umieścić go w makeSmiley, aby makeSmiley zwrócił smileDeco(classmethod(val)). (Sposób, w jaki chcesz to zrobić, zależy od tego, jak ściśle powiązany jest dekorator uśmiechu z rzeczami będącymi metodami klasowymi.) Oznacza to, że nie musisz używać klasy @classmethod.

Oczywiście, w pętli w makeSmiley możesz dołączyć dowolną logikę, którą chcesz wybrać (na przykład na podstawie nazwy metody), czy chcesz ją zawinąć z zachowaniem uśmiechu, czy nie.

Pamiętaj, że musisz być trochę bardziej ostrożny, jeśli naprawdę chcesz ręcznie używać klasy @classmethod, ponieważ metody klasy dostępnej za pośrednictwem klasy __dict__ nie można wywoływać. Musiałbyś więc dokładnie sprawdzić, czy obiekt jest obiektem klasy, zamiast sprawdzać, czy jest możliwy do wywołania.

+0

To wygląda bardzo pomocniczo. Próbuję go teraz uruchomić i udostępnię tutaj aktualizację, jeśli uda mi się ją usunąć. – jononomo

+0

Jeśli mam tuziny klas, które podklasują moją klasę nadrzędną, to muszę pamiętać, aby ozdobić każdą klasę. Wolałbym mieć po prostu logikę, która dostosowuje metody automatycznie w metodzie '__init __()'. Moje klasy potomne nie będą nadpisywać __init __(), więc '__init __()' zdefiniowany w klasie nadrzędnej będzie zawsze wywoływany, gdy skonstruowana zostanie klasa potomna. Gdybym mógł umieścić tam podobną logikę, miałbym pożądaną funkcjonalność. Więc nad tym właśnie pracuję. – jononomo

+0

@JonCrowell: Możesz to zrobić, ale '__init__' jest wywoływany dla każdej * instancji *, a nie dla każdej klasy, więc nie wygląda na to, co chcesz. Jeśli chcesz, aby zachowanie było w pełni automatyczne, musisz użyć metaklasu. Możesz przeczytać na temat metaclasses [tutaj] (http://docs.python.org/2/reference/datamodel.html#customizing-class-creation) i [tutaj] (http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python). – BrenBarn

0

To rozwiązanie powoduje wyjście pragnę:

class ParentObject(object): 
    def __init__(self): 
     self._adjust_methods(self.__class__) 

    def _adjust_methods(self, cls): 
     for attr, val in cls.__dict__.iteritems(): 
      if callable(val) and not attr.startswith("_"): 
       setattr(cls, attr, self._smile_warmly(val)) 
     bases = cls.__bases__ 
     for base in bases: 
      self._adjust_methods(base) 

    def _smile_warmly(self, the_method): 
     def _wrapped(self, *args, **kwargs): 
      print "-smile_warmly - " +self.__name__ 
      the_method(self, *args, **kwargs) 
     cmethod_wrapped = classmethod(_wrapped) 
     # cmethod_wrapped.adjusted = True 
     return cmethod_wrapped 

    def greetings(self): 
     print "greetings" 

class SonObject(ParentObject): 
    def hello_son(self): 
     print "hello son" 

    def goodbye(self): 
     print "goodbye son" 

class DaughterObject(ParentObject): 
    def hello_daughter(self): 
     print "hello daughter" 

    def goodbye(self): 
     print "goodbye daughter" 

if __name__ == '__main__': 
    son = SonObject() 
    son.greetings() 
    son.hello_son() 
    son.goodbye() 
    daughter = DaughterObject() 
    daughter.greetings() 
    daughter.hello_daughter() 
    daughter.goodbye() 

Wyjście jest:

-smile_warmly - SonObject 
greetings 
-smile_warmly - SonObject 
hello son 
-smile_warmly - SonObject 
goodbye son 
-smile_warmly - DaughterObject 
greetings 
-smile_warmly - DaughterObject 
hello daughter 
-smile_warmly - DaughterObject 
goodbye daughter 
+0

Spowoduje to problemy, jeśli utworzysz instancję więcej niż jeden raz, ponieważ spowoduje to ponowne zawinięcie już zapakowanych metod. Przynajmniej powinieneś ustawić flagę każdej klasy podczas jej zawijania, aby oznaczyć ją jako już zapakowaną. Następnie możesz sprawdzić flagę na początku i pominąć zawijanie, jeśli klasa jest już opakowana. – BrenBarn

+0

Hmm - Myślę, że mogłem spieprzyć. Mój podany tu przykładowy plik daje wynik, który chcę, ale mój prawdziwy kod, który jest bardziej skomplikowany, daje mi oszałamiający komunikat "TypeError:" NoneType ", który nie może zostać wywołany. – jononomo

+0

Możesz chcieć opublikować nowe pytanie z tym problemem. – BrenBarn

Powiązane problemy