2016-10-21 21 views
6

Próbuję dodać dekorator, który dodaje atrybuty wywoływalne do funkcji, które zwracają obiekty nieco inne niż wartość zwracana przez funkcję, ale wykonają tę funkcję w pewnym momencie.Uzyskiwanie dostępu do siebie w atrybucie funkcji

Problem, który napotykam, polega na tym, że kiedy obiekt funkcji jest przekazywany do dekoratora, jest on niezwiązany i nie zawiera niejawnego argumentu self. Kiedy zadzwonię do utworzonej funkcji atrybutu (tj. string()), nie mam dostępu do self i nie mogę przekazać jej do oryginalnej funkcji.

def deco(func): 
    """ 
    Add an attribute to the function takes the same arguments as the 
    function but modifies the output. 
    """ 
    def string(*args, **kwargs): 
     return str(func(*args, **kwargs)) 
    func.string = string 
    return func 


class Test(object): 

    def __init__(self, value): 
     self._value = 1 

    @deco 
    def plus(self, n): 
     return self._value + n 

Kiedy idę do wykonania atrybut utworzony przez dekoratora, jest to błąd pojawia się, ponieważ args nie zawiera odniesienia self.

>>> t = Test(100) 
>>> t.plus(1)   # Gets passed self implicitly 
101 
>>> t.plus.string(1) # Does not get passed self implicitly 
... 
TypeError: plus() takes exactly 2 arguments (1 given) 

Czy istnieje sposób na stworzenie takiego dekoratora, który może uzyskać odniesienie do self? Czy istnieje sposób, aby powiązać dodaną funkcję atrybutu (string()), aby również zostanie wywołana z niejawnym argumentem self?

+0

Musisz przekazać wartość 'do' calss w momencie tworzenia! w przeciwnym razie wywoła "TypeError: __init __() pobiera dokładnie 2 argumenty (1 dany)'. – Kasramvd

+0

Interesująca koncepcja. Jeśli chodzi o mnie, mam pewne wątpliwości, że jest to w 100% możliwe, ale będę na pewno sprawdzał odpowiedzi. – agg3l

+0

@Kasramvd Tak, to było literówka w pytaniu, teraz jest naprawione. –

Odpowiedz

6

Można użyć descriptors tutaj:

class deco(object): 

    def __init__(self, func): 
     self.func = func 
     self.parent_obj = None 

    def __get__(self, obj, type=None): 
     self.parent_obj = obj 
     return self 

    def __call__(self, *args, **kwargs): 
     return self.func(self.parent_obj, *args, **kwargs) 

    def string(self, *args, **kwargs): 
     return str(self(*args, **kwargs)) 


class Test(object): 

    def __init__(self, value): 
     self._value = value 

    @deco 
    def plus(self, n): 
     return self._value + n 

tak, aby:

>>> test = Test(3) 
>>> test.plus(1) 
4 
>>> test.plus.string(1) 
'4' 

To gwarantuje wyjaśnienia. deco jest dekoratorem, ale jest również descriptor. Deskryptor to obiekt, który definiuje alternatywne zachowanie, które ma zostać wywołane, gdy obiekt jest postrzegany jako atrybut jego obiektu nadrzędnego. Co ciekawe, same metody ograniczania są implementowane za pomocą protokołu deskryptora

To jest kęs. Spójrzmy, co się dzieje, gdy uruchomimy przykładowy kod. Po pierwsze, gdy definiujemy metodę plus, stosujemy dekorator deco. Teraz normalnie widzimy funkcje jako dekoratory, a wartość zwracana funkcji jest wynikiem dekorowanym. Tutaj używamy klasy jako dekoratora. W rezultacie, Test.plus nie jest funkcją, ale raczej instancją o numerze typu deco. Ta instancja zawiera odwołanie do funkcji plus, którą chcemy zawijać.

Klasa ma metodę __call__, która umożliwia jej działanie w podobny sposób. Implementacja ta po prostu przekazuje argumenty podane dla funkcji plus, do której odwołuje się.Zauważ, że pierwszym argumentem będzie odwołanie do instancji Test.

Podstępną częścią jest implementacja test.plus.string(1). Aby to zrobić, potrzebujemy odwołania do instancji test, której instancją jest instancja plus. Aby to osiągnąć, używamy protokołu deskryptora. Oznacza to, że definiujemy metodę __get__, która będzie wywoływana za każdym razem, gdy instancja deco będzie uzyskiwana jako atrybut pewnej instancji klasy nadrzędnej. Kiedy tak się dzieje, przechowuje on obiekt macierzysty wewnątrz siebie. Następnie możemy po prostu zaimplementować plus.string jako metodę w klasie deco i użyć odwołania do obiektu nadrzędnego przechowywanego w instancji deco, aby uzyskać instancję test, do której należy plus.

To dużo magii, więc oto zastrzeżenie: Choć wygląda to świetnie, prawdopodobnie nie jest dobrym pomysłem, aby zaimplementować coś takiego.

4

Musisz udekorować swoją funkcję w momencie tworzenia instancji (przed utworzeniem metody instancji). Można to zrobić poprzez nadpisanie metody __new__:

class Test(object): 

    def __new__(cls, *args_, **kwargs_): 
     def deco(func): 
      def string(*args, **kwargs): 
       return "my_str is :" + str(func(*args, **kwargs)) 
      # *1 
      func.__func__.string = string 
      return func 

     obj = object.__new__(cls, *args_, **kwargs_) 
     setattr(obj, 'plus', deco(getattr(obj, 'plus'))) 
     return obj 

    def __init__(self, value): 
     self._value = 1 

    def plus(self, n): 
     return self._value + n 

Demo:

>>> t = Test(100) 
>>> t.plus(1) 
>>> t.plus.string(5) 
>>> 'my_str is :6' 

1. Od python nie pozwala na dostęp do atrybut prawdziwy przykład co godzinę Umożliwia ustawienie użyj metody __func__ w celu uzyskania dostępu do rzeczywistego obiektu funkcji metody instancji.

+0

Dzięki za pomysł! Naprawdę nie podoba mi się to, że przełamuje paradygmat dekoratora. Abyś mógł spojrzeć na definicje funkcji (zobacz, że są one dekorowane przez składnię @) i szybko zorientuj się, że mają atrybuty tezy. –