Czy w Pythonie istnieje sposób na powiązanie niezwiązanej metody bez jej wywoływania?
Piszę program wxPython i dla pewnej klasy zdecydowałem, że fajnie byłoby zgrupować dane wszystkich moich przycisków razem jako listę krotek na poziomie klasy, na przykład:
class MyWidget(wx.Window):
buttons = [("OK", OnOK),
("Cancel", OnCancel)]
# ...
def Setup(self):
for text, handler in MyWidget.buttons:
# This following line is the problem line.
b = wx.Button(parent, label=text).Bind(wx.EVT_BUTTON, handler)
Problem w tym, że ponieważ wszystkie wartości handler
są niezwiązane z metodami, mój program wybucha spektakularnym płomieniem i płaczę.
Szukałem w Internecie rozwiązania tego, co wydaje się być stosunkowo prostym, możliwym do rozwiązania problemem. Niestety nic nie mogłem znaleźć. W tej chwili używam functools.partial
do obejścia tego problemu, ale czy ktoś wie, czy istnieje czysty, zdrowy, Pythonowy sposób powiązania niezwiązanej metody z instancją i kontynuowania przekazywania jej bez wywoływania jej?
Odpowiedzi:
Wszystkie funkcje są również deskryptorami , więc możesz je powiązać, wywołując ich
__get__
metodę:Oto doskonały przewodnik R. Hettingera po deskryptorach.
Jako samodzielny przykład zaczerpnięty z komentarza Keitha :
źródło
MethodType
jedno, ponieważ działa tak samo w py3k, aMethodType
argumenty zostały nieco zmienione.bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))
Przykład:class A: pass;
a = A();
bind(a, bind, 'bind')
__get__
zostanie to niejawnie uwzględnione w parametrze obiektu. Nie jestem nawet pewien, czy dostarczenie tego spowoduje cokolwiek, ponieważ nie ma znaczenia, jaki typ podam jako drugi parametr, niezależnie od tego, którego wystąpieniem jest pierwszy parametr. Więc teżbind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))
powinien załatwić sprawę. (Chociaż wolałbym miećbind
użyteczne jako dekorator, osobiście, ale to inna sprawa.)__dict__
a dostęp do atrybutów zapewnia niezwiązane lub powiązane metody przez normalny protokół deskryptora. Zawsze zakładałem, że to jakaś magia wydarzyła się podczastype.__new__()
Można to zrobić czysto za pomocą types.MethodType . Przykład:
źródło
__get__
.). Nie wiem, dla której wersji Pythona to przetestowałeś, ale w Pythonie 3.4MethodType
funkcja przyjmuje dwa argumenty. Funkcja i instancja. Więc to powinno zostać zmienione natypes.MethodType(f, C())
.wgt.flush = types.MethodType(lambda self: None, wgt)
Utworzenie domknięcia zawierającego siebie nie będzie technicznie wiązało funkcji, ale jest alternatywnym sposobem rozwiązania tego samego (lub bardzo podobnego) podstawowego problemu. Oto trywialny przykład:
źródło
functools.partial(handler, self)
Spowoduje to powiązanie
self
zhandler
:Działa to przekazując
self
jako pierwszy argument funkcji.object.function()
jest tylko cukrem syntaktycznymfunction(object)
.źródło
functools.partial
zamiast definiować lambdę. Nie rozwiązuje to jednak dokładnego problemu. Nadal masz do czynienia zfunction
zamiastinstancemethod
.function
którego pierwszy argument częściowo rozwiązałeś, ainstancemethod
; pisanie kaczką nie widzi różnicy.functools.partial
porzuca niektóre metadane, np__module__
. (Chciałbym również zaznaczyć, że bardzo się wzdrygam, kiedy patrzę na mój pierwszy komentarz do tej odpowiedzi.) W rzeczywistości w moim pytaniu wspominam, że już używam,functools.partial
ale czułem, że musi istnieć „czystszy” sposób, ponieważ łatwo jest uzyskać zarówno metody niezwiązane, jak i powiązane.Spóźniłem się na imprezę, ale przyszedłem tutaj z podobnym pytaniem: mam metodę klasową i instancję i chcę zastosować instancję do metody.
Ryzykując nadmierne uproszczenie pytania OP, skończyło się na zrobieniu czegoś mniej tajemniczego, co może być przydatne dla innych, którzy tu przyjeżdżają (uwaga: pracuję w Pythonie 3 - YMMV).
Rozważ tę prostą klasę:
Oto, co możesz z tym zrobić:
źródło
meth
być wywoływalny bez konieczności wysyłaniaa
argumentu (dlatego początkowo użyłemfunctools.partial
) - ale jest to lepsze, jeśli nie musisz przekazywać metody i możesz po prostu wywołaj go na miejscu. Działa to również w ten sam sposób w Pythonie 2, jak w Pythonie 3.