2012-11-15 15 views
9

Próbuję zaimplementować klasę dekoratora, która będzie dekorować metody w innych klasach. Potrzebuję jednak klasy, która posiada udekorowaną metodę dostępną w dekoratorze. Nigdzie nie mogę tego znaleźć.Jak znaleźć klasę zawierającą udekorowaną metodę w Pythonie

Oto przykład:

class my_decorator(object): 

    def __init__(self, arg1, arg2): 
    print(self.__class__.__name__ + ".__init__") 
    self.arg1 = arg1 
    self.arg2 = arg2 

    def __call__(self, my_callable): 
    print(self.__class__.__name__ + ".__call__") 
    print(type(my_callable)) 
    self.my_callable = my_callable 
# self.my_callable_method_class = ?where to get this? 

    def function_wrapper(*args, **kwargs): 
     print(self.__class__.__name__ + ".function_wrapper") 
     print(self.arg1) 
     self.my_callable.__call__(*args, **kwargs) 
     print(self.arg2) 

    return function_wrapper 


class MyClass(object): 

    @my_decorator(arg1="one", arg2="two") 
    def decorated_method(self): 
    print(self.__class__.__name__ + ".decorated_method") 
    print(type(self.decorated_method)) 
    print("hello") 


m = MyClass() 
m.decorated_method() 

To będzie wydrukować to:

my_decorator.__init__ 
my_decorator.__call__ 
<type 'function'> 
my_decorator.function_wrapper 
one 
MyClass.decorated_method 
<type 'instancemethod'> 
hello 
two 

W klasie dekorator wymagalne jest funkcją typu, podczas gdy wewnątrz samej klasy jest typu instancemethod. Mogę pobrać instancję instancji z instancemethod, ale nie ma czegoś takiego w funkcji.

Jak uzyskać klasę zawierającą dekorowaną metodę od dekoratora?

mogę to zrobić:

class my_decorator(object): 

    def __init__(self, cls, arg1, arg2): 

. 
. 

class MyClass(object): 

    @my_decorator(cls=MyClass, arg1="one", arg2="two") 
    def decorated_method(self): 

. 
. 

Ale nie chciałbym tego robić, bo to zbędne i nie miły.

Czy powinienem to wdrożyć w inny sposób? W zasadzie potrzebuję kilku argumentów dla dekoratora i potrzebuję klasy dekorowanej metody w dekoratorze.

+0

Kiedy dokładnie potrzebujesz lekcji? Możesz zmodyfikować wewnętrzne opakowanie do 'def function_wrapper (self, * args, ** kwargs)' i uzyskać klasę jako 'self .__ class__'. Jeśli potrzebujesz klasy poza dekoratorem, to jest ona o wiele trudniejsza, niż wskazywał już katrielalex. – Bakuriu

Odpowiedz

3

Można udekorować klasę :

@decorate 
class MyClass(object): 

    @my_decorator(arg1="one", arg2="two") 
    def decorated_method(self): 

i użyć zewnętrznego dekoratora wysłać klasy argumentu do wewnętrznej.


Żadna z propozycji może pracować, ponieważ wymagają one dostęp do klasy przed istnieje. Kiedy definiujesz klasę, najpierw wykonaj kod wewnątrz jej ciała (definiując funkcje itd.), A następnie przypisz wynikowy zakres do klasy jako __dict__. W związku z tym, że zdefiniowano decorated_method, MyClass jeszcze nie istnieje.

+0

To wygląda jak droga. Ale w jaki sposób mogę połączyć dekorator klas i dekorator metod? Będę miał wiele klas i metod używających tych samych dekoratorów ... – kortsi

+0

'@ my_decorator' powinien oznaczyć metody, które zostały urządzone, np. Nadając im specjalny atrybut. '@ dekoruj' powinien powtarzać wszystkie metody klasy, filtrując te bez specjalnego atrybutu i wykonywać po kolei ich dekoracje. – katrielalex

+0

Wygląda na to, że działa. Dzięki! – kortsi

0

Jeśli obiekt zwrócony przez dekorator będzie miał postać descriptor, można przełączyć wyszukiwanie atrybutu, aby zwrócić inny obiekt, który łączy metodę i klasę (lub wystąpienie).

Dla deskryptora stylu metody wystarczy zastosować metodę __get__. Kiedy patrzy się na metodę na klasy, następujące dwie są równoważne:

m = MyClass.decorated_method 
# It will actually get the object from any parent class too. But this will do for a simple example 
m = MyClass.__dict__['decorated_method'].__get__(MyClass) 

I instancji, następujące są równoważne:

instance = MyClass() 
m = instance.decorated_method 
m = type(instance).__dict__['decorated_method'].__get__(instance, type(instance)) 

Więc wyrażenie instance.decorated_method(...) faktycznie wywołuje obiekt zwrócony przez __get__ metoda. Jest to ten sam proces, który umożliwia przekształcenie prostych obiektów funkcji w powiązane obiekty metod, które dodają niejawny argument self.

Podczas tworzenia tej opcji należy uzyskać wszystkie potrzebne informacje.

+0

Problem z tym jest taki, że muszę pracować z klasą, zanim utworzę pierwszą instancję. – kortsi

+0

Wystarczająco fair. Wtedy prawdopodobnie będziesz potrzebował dekoratora, który zaznacza funkcje takie jak sugerowane przez katrielalex, a następnie napraw je albo z dekoratora klasy, albo z konstruktora metaclass. –

1

Oto poprawiona wersja, która działa.

# This holds all called method_decorators 
global_method_decorator_list = [] 

class class_decorator(object): 
    def __init__(self, arg1, arg2): 
    print(self.__class__.__name__ + ".__init__") 
    self.arg1 = arg1 
    self.arg2 = arg2 

    def __call__(self, my_class): 
    print(self.__class__.__name__ + ".__call__") 
    print(repr(my_class)) 
    print(my_class.__name__) 
    self.cls = my_class 
    class_decorators[my_class] = self 
    self.my_class = my_class 

    # Call each method decorator's second_init() 
    for d in global_method_decorator_list: 
     d._method_decorator_.second_init(self, my_class) 

    def wrapper(*args, **kwargs): 
     print(self.__class__.__name__ + ".wrapper") 
     print(self.arg1) 
     retval = self.my_class.__call__(*args, **kwargs) 
     print(self.arg2) 
     return retval 

    return wrapper 


class method_decorator(object): 
    def __init__(self, arg1, arg2): 
    print(self.__class__.__name__ + ".__init__") 
    self.arg1 = arg1 
    self.arg2 = arg2 

    def __call__(self, my_callable): 
    print(self.__class__.__name__ + ".__call__") 
    print(repr(my_callable)) 
    self.my_callable = my_callable 

    # Mark the callable and add to global list 
    my_callable._method_decorator_ = self 
    global_method_decorator_list.append(my_callable) 

    def wrapper(*args, **kwargs): 
     print(self.__class__.__name__ + ".wrapper") 
     print(self.arg1) 
     retval=self.my_callable.__call__(*args, **kwargs) 
     print(self.arg2) 
     return retval 

    return wrapper 

    def second_init(self, the_class_decorator, the_class): 
    print(self.__class__.__name__ + ".second_init") 
    print("The Class: " + repr(the_class))** 


@class_decorator(arg1="One", arg2="Two") 
class MyClass(object): 

    @method_decorator(arg1="one", arg2="two") 
    def decorated_method(self): 
    print(self.__class__.__name__ + ".decorated_method") 
    print(type(self.decorated_method)) 
    print("hello") 


m = MyClass() 
m.decorated_method() 

Wyjście wygląda następująco:

class_decorator.__init__ 
method_decorator.__init__ 
method_decorator.__call__ 
<function decorated_method at 0x3063500> 
class_decorator.__call__ 
<class '__main__.MyClass'> 
MyClass 
method_decorator.second_init 
The Class: <class '__main__.MyClass'> 
class_decorator.wrapper 
One 
Two 
method_decorator.wrapper 
one 
MyClass.decorated_method 
<type 'instancemethod'> 
hello 
two 

Różnica polega na tym, że nie ma teraz osobny dekorator dla klasy. Wywoływacz klasy wywołanie() wywoła każdy z metod dekoratorów metodą "drugi_init()" i przekazuje tam klasę.

Warto zauważyć, że wywołanie metody method_decorator wywołanie() zostanie wywołane przed klasą_decorator.

Powiązane problemy