2011-02-03 15 views
8

W Pythonie 3 można użyć super() zamiast super(MyClass, self), ale działa to tylko w metodach, które zostały zdefiniowane w klasie. Jak opisano w Michele Simionato's article następujący przykład nie działa:Jak wykonać super() pracę, ręcznie wypełniając komórkę __class__?

def __init__(self): 
    print('calling __init__') 
    super().__init__() 

class C(object): 
    __init__ = __init__ 

if __name__ == '__main__': 
    c = C() 

To nie działa, ponieważ super() szuka __class__cell, który nie jest zdefiniowany w tej sprawie.

Czy można ustawić tę komórkę ręcznie po zdefiniowaniu funkcji, czy jest to niemożliwe?

Niestety nie rozumiem, jak komórki działają w tym kontekście (nie znalazłem zbyt wiele dokumentacji). Mam nadzieję, że za coś takiego

__init__.__class_cell_thingy__ = C 

Oczywiście chciałbym wykorzystać to tylko w sytuacji, gdy klasa jest jednoznaczne przyporządkowanie/wyjątkowy (cały proces dodawania metody w klasie jest zautomatyzowana w moim przypadku, tak byłoby łatwo dodać taką linię).

+3

"Czy można ustawić tę komórkę ręcznie"? Kiedy próbowałeś tego, co się stało? –

+0

Ale jak mogę uzyskać dostęp do tej komórki po zdefiniowaniu funkcji? '__init __.__ class__' to oczywiście coś innego. Zmieniłem to pytanie, aby to wyjaśnić. – nikow

+0

Dlaczego chcesz zdefiniować funkcję poza klasą? Chcesz tego, jeśli chcesz ponownie użyć funkcji lub użyć funkcji jako metody wiele razy. Nie możesz tego zrobić i poprawnie używać 'super (...)'. A jeśli modyfikujesz klasę w środowisku wykonawczym, możesz po prostu ją zakodować. –

Odpowiedz

15

Poważnie: naprawdę nie chcesz tego robić.

Ale jest to przydatne dla zaawansowanych użytkowników Pythona, aby to zrozumieć, więc wytłumaczę to.

Komórki i freevary to wartości przypisane podczas tworzenia zamknięcia. Na przykład,

def f(): 
    a = 1 
    def func(): 
     print(a) 
    return func 

f zwraca zamknięcia w oparciu o func, przechowywania odniesienie do a. To odniesienie jest przechowywane w komórce (w rzeczywistości w freevar, ale która jest zależna od implementacji). Można sprawdzić w ten sposób:...

myfunc = f() 
# ('a',) 
print(myfunc.__code__.co_freevars) 
# (<cell at 0xb7abce84: int object at 0x82b1de0>,) 
print(myfunc.__closure__) 

(„komórki” i „freevars” są bardzo podobne Freevars mają nazwy, gdzie komórki mają indeksy Oboje są przechowywane w func.__closure__, z komórek pochodzących najpierw tylko dbają o freevars tutaj, ponieważ to jest __class__.)

Kiedy to zrozumiesz, zobaczysz, jak działa super(). Każda funkcja, która zawiera wezwanie do super jest faktycznie zamknięcie, z freevar nazwie __class__ (który dodaje również, jeśli odnoszą się do __class__ siebie):

class foo: 
    def bar(self): 
     print(__class__) 

(Ostrzeżenie:. To jest, gdy robi się zły)

Te komórki są widoczne w func.__closure__, ale są tylko do odczytu; nie możesz tego zmienić. Jedynym sposobem na jej zmianę jest utworzenie nowej funkcji, która jest wykonywana z konstruktorem types.FunctionType. Jednak twoja funkcja __init__ nie ma w ogóle freevar __class__ - więc musimy dodać. To oznacza, że ​​musimy również utworzyć nowy obiekt kodu.

Poniższy kod to robi. Dodałem podstawową klasę B dla celów poglądowych. Ten kod zawiera pewne założenia, np. że __init__ nie ma już wolnej zmiennej o nazwie __class__.

Tutaj jest inny hack: nie wydaje się być konstruktorem dla typu komórki. Aby obejść ten problem, tworzona jest fikcyjna funkcja C.dummy, która zawiera potrzebną zmienną komórki.

import types 

class B(object): 
    def __init__(self): 
     print("base") 

class C(B): 
    def dummy(self): __class__ 

def __init__(self): 
    print('calling __init__') 
    super().__init__() 

def MakeCodeObjectWithClass(c): 
    """ 
    Return a copy of the code object c, with __class__ added to the end 
    of co_freevars. 
    """ 
    return types.CodeType(c.co_argcount, c.co_kwonlyargcount, c.co_nlocals, 
      c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names, 
      c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno, 
      c.co_lnotab, c.co_freevars + ('__class__',), c.co_cellvars) 

new_code = MakeCodeObjectWithClass(__init__.__code__) 
old_closure = __init__.__closure__ or() 
C.__init__ = types.FunctionType(new_code, globals(), __init__.__name__, 
    __init__.__defaults__, old_closure + (C.dummy.__closure__[0],)) 

if __name__ == '__main__': 
    c = C() 
+0

Dzięki, doskonała odpowiedź. Miło wiedzieć, jak te rzeczy działają. Myślę, że wezmę twoją radę i trzymam się teraz wyraźnego "super". Dopóki Python 3 nie stanie się dominujący, to i tak jest nieco akademicki. – nikow

+0

P.S. Wydaje się, że ta odpowiedź popchnęła cię do 10k rep :-) – nikow

+1

Inną sztuczką jest zdefiniowanie funkcji wewnątrz innej funkcji za pomocą pojedynczego argumentu '__class__', a następnie zwrócenie funkcji wewnętrznej (lub tylko jej zamknięcie). W takim razie nie potrzebujesz metody "dummy" do stworzenia zamknięcia, możesz równie dobrze zdefiniować właściwą metodę jako taką wewnętrzną funkcję. Inną rzeczą jest to, że do przechowywania funkcji można użyć kontenera klasy (prawdopodobnie z dodatkowym metaclass z metodami ukrywania magii), wtedy nie trzeba tworzyć niestandardowych obiektów kodu z tym przerażającym wywołaniem. –

1

Może, ale czy zrobiłbyś to? W obu przypadkach musisz w jakiś sposób określić, która to klasa, ponieważ niejawny sposób nie zadziałał. Możliwe, że możesz wyraźnie ustawić komórkę, ale nie ma powodu, aby to robić. Wystarczy jawnie przekazać parametry.

def __init__(self): 
    print('calling __init__') 
    super(self.__class__, self).__init__() 

class C(object): 
    __init__ = __init__ 

if __name__ == '__main__': 
    c = C() 

(Lepiej, jeśli można przekazać w rzeczywistej klasy bezpośrednio, tak jak poniżej:

def __init__(self): 
    print('calling __init__') 
    super(C, self).__init__() 

class C(object): 
    __init__ = __init__ 

if __name__ == '__main__': 
    c = C() 

Ale jeśli można, że ​​można umieścić __init__ na C bezpośrednio, więc zakładamy, można” t.

+2

Dzięki za odpowiedź, ale to, co tu sugerujesz, jest bardzo złe. Nie możesz użyć 'self .__ class__' z' super', co ostatnio zostało wyjaśnione tutaj: http://stackoverflow.com/questions/4883822/shortcut-for-supertypeself-self – nikow

+0

@nikow: Cóż, jeśli idziesz do podklasy można dostać się do problemów, tak, jeśli ta podklasa również używa super(). Więc chcesz użyć tego samego '__init__' dla wielu różnych klas (bo inaczej nie zdefiniowałbyś tego poza klasą) i nie wiesz, które zdania, i nie wiesz, czy i jak te klasy są z podklasą? W takim przypadku chciałbym powiedzieć, że rozwiązujecie problem nieprawidłowo. Czy mogę zaproponować dekorator klas lub architekturę komponentów z adapterami? –

+0

@Lennart: Dodana funkcja jest zdefiniowana gdzie indziej w następstwie pomysłów z AOP, a to działało całkiem dobrze w naszym projekcie. W prawdziwym kodzie te "aspekty" są aktywowane/dezaktywowane w czasie wykonywania, więc używanie klas dekoratorów nie jest tak naprawdę opcją. Miałem tylko nadzieję, że można włączyć uproszczone wywołania 'super()', po tym wszystkim zostały dodane do Pythona 3 z jakiegoś powodu. – nikow

2

można użyć słownika funkcji.

def f(self): 
    super(f.owner_cls, self).f() 
    print("B") 

def add_to_class(cls, member, name=None): 
    if hasattr(member, 'owner_cls'): 
     raise ValueError("%r already added to class %r" % (member, member.owner_cls)) 
    member.owner_cls = cls 
    if name is None: 
     name = member.__name__ 
    setattr(cls, name, member) 

class A: 
    def f(self): 
     print("A") 

class B(A): 
    pass 

add_to_class(B, f) 

B().f() 

Możesz nawet dodać kolejny atrybut member_name, jeśli nie chcesz, aby kod był twardy w nazwie nazwy elementu wewnątrz funkcji.

+0

To by działało, ale nie jestem pewien, czy wolałbym to od jawnego rozwiązania z klasą pisania. Przecież ja * wiem * do której klasy metoda będzie należeć, po prostu chcę skorzystać z wygodniejszego Python 3 'super()'. – nikow

+2

Jeśli robisz coś takiego w pierwszej kolejności, prawdopodobnie będziesz chciał móc przypisać funkcję do więcej niż jednej klasy. –

+0

@Glenn: Tak, masz rację. Chociaż nie jest to mój główny przypadek użycia, przypomniałem sobie, że są przypadki, w których byłby to problem. – nikow

Powiązane problemy