2013-03-22 23 views
5

Trudno mi zrozumieć, co dzieje się, gdy próbuję zagnieździć deskryptory/dekoratory. Używam Pythona 2.7.Zagnieżdżanie deskryptorów/dekoratorów w pythonie

Na przykład, weźmy następujące uproszczone wersje property i classmethod:

class MyProperty(object): 
    def __init__(self, fget): 
     self.fget = fget 
    def __get__(self, obj, objtype=None): 
     print 'IN MyProperty.__get__' 
     return self.fget(obj) 

class MyClassMethod(object): 
    def __init__(self, f): 
     self.f = f 
    def __get__(self, obj, objtype=None): 
     print 'IN MyClassMethod.__get__' 
     def f(*args, **kwargs): 
      return self.f(objtype, *args, **kwargs) 
     return f 

Starając się zagnieździć je:

class A(object): 
    # doesn't work: 
    @MyProperty 
    @MyClassMethod 
    def klsproperty(cls): 
     return 555 
    # works: 
    @MyProperty 
    def prop(self): 
     return 111 
    # works: 
    @MyClassMethod 
    def klsmethod(cls, x): 
     return x**2 

% print A.klsproperty 
IN MyProperty.__get__ 
... 
TypeError: 'MyClassMethod' object is not callable 

__get__ metoda wewnętrznej deskryptorze MyClassMethod nie nazywa uzyskiwanie. W przeciwnym razie, aby dowiedzieć się, dlaczego, próbowałem rzucać się (co moim zdaniem jest) deskryptor no-op:

class NoopDescriptor(object): 
    def __init__(self, f): 
     self.f = f 
    def __get__(self, obj, objtype=None): 
     print 'IN NoopDescriptor.__get__' 
     return self.f.__get__(obj, objtype=objtype) 

próbuje użyć no-op deskryptora/dekorator w zagnieżdżania

class B(object): 
    # works: 
    @NoopDescriptor 
    @MyProperty 
    def prop1(self): 
     return 888 
    # doesn't work: 
    @MyProperty 
    @NoopDescriptor 
    def prop2(self): 
     return 999 

% print B().prop1 
IN NoopDescriptor.__get__ 
IN MyProperty.__get__ 
888 
% print B().prop2 
IN MyProperty.__get__ 
... 
TypeError: 'NoopDescriptor' object is not callable 

Nie rozumiem, dlaczego B().prop1 działa, a B().prop2 nie.

Pytania:

  1. Co robię źle? Dlaczego otrzymuję komunikat o błędzie object is not callable?
  2. Jaka jest właściwa droga? na przykład co jest najlepszym sposobem definiowania MyClassProperty natomiast ponowne wykorzystanie MyClassMethod i MyProperty (lub classmethod i property)
+1

Kluczem do zrozumienia tego intuicyjnie jest to, że '@ MojaProperty' tworzy coś, co działa jak atrybut _data_, a nie metoda, więc nie można zagnieździć go za pomocą deskryptora metody. To intuicyjne zrozumienie dociera tylko do ciebie; cała historia jest w odpowiedzi Isedeva. – abarnert

Odpowiedz

3

Można zrobić swoją pracę kodu jeśli się MyProperty zastosować protokół deskryptora jego owinięte obiektu:

class MyProperty(object): 
    def __init__(self, fget): 
     self.fget = fget 
    def __get__(self, obj, objtype=None): 
     print('IN MyProperty.__get__') 
     try: 
      return self.fget.__get__(obj, objtype)() 
     except AttributeError: # self.fget has no __get__ method 
      return self.fget(obj) 

Teraz Twój przykładowy kod działa:

class A(object): 
    @MyProperty 
    @MyClassMethod 
    def klsproperty(cls): 
     return 555 

print(A.klsproperty) 

Wyjście jest:

IN MyProperty.__get__ 
IN MyClassMethod.__get__ 
555 
+0

Dzięki, to ma sens. Ale czy jest to zalecane podejście? Jeśli tak, to w jaki sposób wbudowane 'własność' i' classmethod' nie zachowują się w ten sposób (i nie mogą być zagnieżdżone)? – shx2

+0

Nie jestem pewien, czy jest to odradzane, lub dlaczego wbudowane 'property' nie robi tego już. Wiem, że istnieją pewne ograniczenia, które nie zostały naprawione. Po pierwsze, nie możesz zagnieździć deskryptorów w odwrotnej kolejności, nawet jeśli uczyniłeś 'MyClassMethod' bardziej sprytnym. I nie sądzę, że funkcja deskryptora '__set__' jest wywoływana do przypisania zmiennych klasowych, więc musisz zrobić magię metaclass, aby zapisywalne właściwości klasy mogły działać.Być może ktoś, kto ma więcej doświadczenia z programowaniem deskryptorów, może odnieść się do innych problemów i ograniczeń. – Blckknght

5

w tym przypadku, gdy dekoratorzy są używane bez parametrów dekorator nazywa z funkcją zdobi jako jego parametr. Wartość zwracana przez dekorator jest używana zamiast funkcji dekorowanej. Więc:

@MyProperty 
def prop(self): 
    ... 

odpowiada:

def prop(self): 
    ... 
prop = MyProperty(prop) 

Od MyProperty implementuje protokół deskryptora, dostępu A.prop rzeczywiście nazwać A.prop.__get__(), a ty zdefiniowane __get__ zadzwonić do obiektu, który został urządzony (w tym case, oryginalna funkcja/metoda), więc wszystko działa poprawnie.

Teraz w zagnieżdżonej przypadku:

@MyProperty 
@MyClassMethod 
def prop(self): 
    ... 

Odpowiednikiem jest:

def prop(self): 
    ... 
prop = MyClassMethod(prop) # prop is now instance of MyClassMethod 
prop = MyProperty(prop)  # prop is now instance of MyProperty 
          # (with fget == MyClassMethod instance) 

Teraz, jak poprzednio, dostępu A.prop rzeczywiście nazwać A.prop.__get__() (w MyProperty), który następnie próbuje rozmowy wystąpienie MyClassMethod (obiekt, który został urządzony i przechowywany w atrybucie fget).

Ale MyClassMethod nie ma zdefiniowanej metody __call__, więc pojawia się błąd MyClassMethod is not callable.


I zająć drugie pytanie: Właściwość jest już atrybutem klasy - w przykładzie, dostęp A.prop zwróci wartość właściwości w obiekcie klasy i A().prop zwróci wartość nieruchomości w sposób obiekt instancji (który może być taki sam jak obiekt klasy, jeśli instancja go nie przesłania).

+0

Twoje wyjaśnienie, jeśli jest jasne. Dziękuję Ci. Odnośnie drugiej odpowiedzi: nie jestem pewien, czy rozumiem, co masz na myśli. Szukam czegoś równoległego do 'classmethod', co oznacza, że ​​mogę go nazwać albo' A.prop' lub 'A(). Prop', aw obu przypadkach podstawową funkcją jest przekazana arg" cls ". – shx2

+0

W takim przypadku możesz sprawdzić typ argumentu "obj": jeśli jest to instancja klasy, możesz wyodrębnić z niej klasę i przekazać ją do dekorowanej funkcji zamiast obiektu instancji. – isedev

+0

Brakuje ci punktu ... Moje pytanie nie dotyczy tego, jak zaimplementować dekorator "classproperty". Chodzi o zagnieżdżanie deskryptorów. Logika, którą sugerujesz, jest już zaimplementowana w 'classmethod'. Spodziewałbym się, że powinien istnieć czysty sposób ponownego użycia/łączenia "właściwości" i "metody klasowej", aby to osiągnąć, zamiast ponownego wcielenia w nie logiki. Ale zaczynam myśleć, że nie ma ... – shx2

0

Znalazłem ostateczną odpowiedź na moje stare pytanie w Graham Dumpl eton fascynuje blog.

W skrócie, dekoratorzy pisałem nie honorować protokół deskryptorów, próbując wywołać zawinięty funkcja/obiekt bezpośrednio, zamiast najpierw dając im szansę, aby wykonać swoją „deskryptora magii” (poprzez wywołanie ich __get__() pierwszy).