2013-08-23 10 views
12

pracuję z somethign podobne do tego kodu:python: spółdzielczy supercall z __getattr__

class BaseClass(object): 
    def __getattr__(self, attr): 
     return lambda:'1' 

class SubClass(BaseClass): 
    def foo(self): 
     suffix = '2' 
     return super(SubClass, self).foo() + suffix 

class SubClass2(SubClass): 
    def foo(self): 
     suffix = '3' 
     return super(SubClass2, self).foo() + suffix 

o = SubClass2() 
print o.foo() 

będę oczekiwać, aby zobaczyć wyjście „123”, ale zamiast tego pojawia się błąd AttributeError: 'super' object has no attribute 'foo'. Python nawet nie próbuje korzystać z klasy podstawowej __getattr__.

Bez modyfikacji klasy bazowej i zachowania dwóch super-wywołań podobnych, nie jestem w stanie uzyskać pożądanego wyniku. Czy istnieje jakiś wspólny wzorzec superklazji, który będzie dla mnie działał?

Rozumiem, że super() zastępuje getattr w jakiś sposób, aby robić to, co trzeba zrobić, ale pytam, czy istnieje jakikolwiek rozsądny obejście, które pozwala podklasą na __getattr__ być wywołana, gdy stosowne.

+0

możliwe duplikat [ 'Super' obiekt nie dzwoni \ _ \ _ getattr \ _ \ _] (http ://przepełnienie stosu.com/questions/12047847/super-object-not-calling-getattr) – Eevee

+1

@eevee Proponowany duplikat zadaje pytanie "dlaczego super nie działa?" mając na uwadze, że proszę o procedurę, która działa, ponieważ super nie zadziała. – bukzor

Odpowiedz

3

To wydaje się działać poprawnie. Obecnie nie widzę powodu, dla którego standardowa super klasa tego nie robi.

class super2(super): 
    def __getattr__(self, attr): 
     return self.__self__.__getattr__(attr) 

class BaseClass(object): 
    def __getattr__(self, attr): 
     return lambda:'1' 

class SubClass(BaseClass): 
    def foo(self): 
     suffix = '2' 
     return super2(SubClass, self).foo() + suffix 

class SubClass2(SubClass): 
    def foo(self): 
     suffix = '3' 
     return super2(SubClass2, self).foo() + suffix 

o = SubClass2() 
print o.foo() 
+0

Zwróć uwagę, że 'object .__ getattr__' nie istnieje, więc złamałoby to i tak delikatny przypadek dziedziczenia diamentu z' obiektu'. może to również umożliwić w pewnych przypadkach metody metaclass ingerujące w rozwiązywanie metody. – Eevee

+0

Powinien sprawdzić 'hasattr (self .__ self__, '__getattr __')' na linii 3 i podnieść wyjątek, jeśli '__getattr__' nie zostanie podany. – Perkins

5

Ach, to jest świetne pytanie!

W skrócie, co się tu dzieje jest to, że wewnętrzne CPython czasami wziąć skróty kiedy robią wyszukiwań atrybutów, a ten rodzaj zachowania jest zaskakujący jedną z konsekwencji (drugi będąc lepszą wydajność).

Aby dokładnie zrozumieć, co dzieje się w tej sytuacji, musimy zaryzykować do definicji super: http://hg.python.org/cpython/file/c24941251473/Objects/typeobject.c#l6689

Zawiadomienie konkretnie, że nie definiuje tp_getattr (aka __getattr__), ale nie definiuje tp_getattro (aka __getattribute__):

PyTypeObject PySuper_Type = { 
    PyVarObject_HEAD_INIT(&PyType_Type, 0) 
    "super",         /* tp_name */ 
    ... 
    0,           /* tp_getattr */ 
    ... 
    super_getattro,        /* tp_getattro */ 
    ... 
}; 

(Przypomnijmy, że __getattribute__ nazywa każdym razem atrybutem jest wniosek, w przeciwieństwie do __getattr__, które nazywa się tylko wtedy, gdy atrybut nie istnieje na obiekcie (w przybliżeniu: jeśli atrybut nie jest w obiektu __dict__)).

Następnie, patrząc na definicję super_getattro (aka super.__getattribute__), widzimy realizacja jest około:

class super(object): 
    def __init__(self, obj_type, obj): 
     self.obj_type = obj_type 
     self.obj = obj 

    def __getattribute__(self, attr): 
     i = self.obj_type.__mro__.find(self.obj_type) 
     i += 1 
     while i < len(obj_type.__mro__): 
      cur_type = self.obj_type.__mro__[i] 
      cur_dict = cur_type.__dict___ 
      res = cur_dict.get(attr) 
      if res is not None: 
       return res 
      i += 1 
     return object.__getattribute__(self, attr) 

Co sprawia, że ​​oczywiste, dlaczego super nie dobrze bawić __getattr__ - super sprawdza tylko atrybuty w klasie nadrzędnej "__dict__!

Zabawa na bok: wydaje się, że pypy (zgodnie z 2.1.0) zachowuje się w ten sam sposób:

$ pypy super.py 
Traceback (most recent call last): 
    File "app_main.py", line 72, in run_toplevel 
    File "super.py", line 16, in <module> 
    print o.foo() 
    File "super.py", line 13, in foo 
    return super(SubClass2, self).foo() + suffix 
    File "super.py", line 8, in foo 
    return super(SubClass, self).foo() + suffix 
AttributeError: 'super' object has no attribute 'foo' 
+0

Świetnie, jasno wyjaśnij moje zamieszanie. – lengxuehx