Python: Eine ungebundene Methode binden?


116

Gibt es in Python eine Möglichkeit, eine ungebundene Methode zu binden, ohne sie aufzurufen?

Ich schreibe ein wxPython-Programm und für eine bestimmte Klasse habe ich beschlossen, dass es schön wäre, die Daten aller meiner Schaltflächen als Liste von Tupeln auf Klassenebene zu gruppieren, wie folgt:

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)

Das Problem ist, da alle Werte von handlerungebundenen Methoden sind, explodiert mein Programm in einem spektakulären Feuer und ich weine.

Ich habe mich online nach einer Lösung umgesehen, die ein relativ einfaches, lösbares Problem sein sollte. Leider konnte ich nichts finden. Im Moment arbeite ich daran functools.partial, das zu umgehen, aber weiß jemand, ob es eine saubere, gesunde und pythonische Methode gibt, eine ungebundene Methode an eine Instanz zu binden und sie weiterzugeben, ohne sie aufzurufen?


6
@Christopher - Eine Methode, die nicht an den Umfang des Objekts gebunden ist, aus dem sie entnommen wurde. Sie müssen sich also explizit übergeben.
Aiden Bell

2
Ich mag besonders "spektakuläre Flamme und ich weine".
Jspencer

Antworten:


173

Alle Funktionen sind auch Deskriptoren , sodass Sie sie durch Aufrufen ihrer __get__Methode binden können :

bound_handler = handler.__get__(self, MyWidget)

Hier ist R. Hettingers exzellenter Leitfaden für Deskriptoren.


Als eigenständiges Beispiel aus Keiths Kommentar :

def bind(instance, func, as_name=None):
    """
    Bind the function *func* to *instance*, with either provided name *as_name*
    or the existing name of *func*. The provided *func* should accept the 
    instance as the first argument, i.e. "self".
    """
    if as_name is None:
        as_name = func.__name__
    bound_method = func.__get__(instance, instance.__class__)
    setattr(instance, as_name, bound_method)
    return bound_method

class Thing:
    def __init__(self, val):
        self.val = val

something = Thing(21)

def double(self):
    return 2 * self.val

bind(something, double)
something.double()  # returns 42

3
Das ist ziemlich toll. Mir gefällt, wie Sie den Typ weglassen und stattdessen eine "gebundene Methode? .F" zurückerhalten können.
Kiv

Ich mag diese Lösung gegenüber der MethodTypeeinen, weil sie in py3k genauso funktioniert, während MethodTypedie Argumente ein wenig geändert wurden.
Bgw

10
Und damit eine Funktion zum Binden von Funktionen an Klasseninstanzen: bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))Beispiel:class A: pass; a = A(); bind(a, bind, 'bind')
Keith Pinson

2
Huh, du lernst jeden Tag etwas Neues. @Kazark Zumindest in Python 3 können Sie auch die Angabe des Typs überspringen, da __get__dies implizit aus dem Objektparameter übernommen wird. Ich bin mir nicht einmal sicher, ob die Bereitstellung etwas bewirkt, da es keinen Unterschied macht, welchen Typ ich als zweiten Parameter anbiete, unabhängig davon, wofür der erste Parameter eine Instanz ist. Also bind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))sollte der Trick auch tun. (Ich würde es zwar vorziehen bind, persönlich als Dekorateur verwendet zu werden, aber das ist eine andere Sache.)
JAB

1
Wow, ich wusste nie, dass Funktionen Deskriptoren sind. Das ist ein sehr elegantes Design. Methoden sind nur einfache Funktionen in der Klasse. Durch den __dict__Attributzugriff erhalten Sie ungebundene oder gebundene Methoden über das normale Deskriptorprotokoll. Ich habe immer angenommen, dass es eine Art Magie war, die währendtype.__new__()
JaredL

81

Dies kann sauber mit types.MethodType erfolgen . Beispiel:

import types

def f(self): print self

class C(object): pass

meth = types.MethodType(f, C(), C) # Bind f to an instance of C
print meth # prints <bound method C.f of <__main__.C object at 0x01255E90>>

10
+1 Das ist großartig, aber es gibt keinen Hinweis darauf in den Python-Dokumenten unter der von Ihnen angegebenen URL.
Kyle Wild

6
+1, ich bevorzuge es, keine Aufrufe von magischen Funktionen in meinem Code zu haben (dh __get__). Ich weiß nicht, für welche Python-Version Sie dies getestet haben, aber für Python 3.4 akzeptiert die MethodTypeFunktion zwei Argumente. Die Funktion und die Instanz. Das sollte also geändert werden in types.MethodType(f, C()).
Dan Milon

Hier ist es! Es ist ein guter Weg, um Instanzmethoden zu patchen:wgt.flush = types.MethodType(lambda self: None, wgt)
Winand

2
Es wird tatsächlich in den Dokumenten erwähnt, aber auf der Deskriptorseite der anderen Antwort: docs.python.org/3/howto/descriptor.html#functions-and-methods
kai

9

Das Erstellen eines Abschlusses mit Selbst darin bindet die Funktion technisch nicht, ist jedoch eine alternative Methode zur Lösung des gleichen (oder sehr ähnlichen) zugrunde liegenden Problems. Hier ist ein triviales Beispiel:

self.method = (lambda self: lambda args: self.do(args))(self)

1
Ja, dies ist ungefähr das gleiche wie mein ursprünglicher Fix, der verwendet werden solltefunctools.partial(handler, self)
Dan Passaro

7

Dies wird binden selfan handler:

bound_handler = lambda *args, **kwargs: handler(self, *args, **kwargs)

Dies funktioniert, indem selfals erstes Argument an die Funktion übergeben wird. object.function()ist nur syntaktischer Zucker für function(object).


1
Ja, aber das ruft die Methode auf. Das Problem ist, dass ich in der Lage sein muss, die gebundene Methode als aufrufbares Objekt zu übergeben. Ich habe die ungebundene Methode und die Instanz, an die ich sie binden möchte, kann aber nicht herausfinden, wie ich alles zusammensetzen kann, ohne sie sofort aufzurufen
Dan Passaro,

9
Nein, das tut es nicht, es ruft die Methode nur auf, wenn Sie bound_handler () ausführen. Das Definieren eines Lambda nennt das Lambda nicht.
Brian-Brasilien

1
Sie könnten tatsächlich functools.partialein Lambda verwenden, anstatt es zu definieren. Es löst jedoch nicht das genaue Problem. Sie haben es immer noch mit einem functionstatt mit einem zu tun instancemethod.
Alan Plum

5
@ Alan: Was ist der Unterschied zwischen einem, functiondessen erstes Argument Sie teilweise bearbeitet haben, und instancemethod; Ententippen kann den Unterschied nicht erkennen.
Lie Ryan

2
@LieRyan der Unterschied ist, dass Sie immer noch nicht mit dem Grundtyp zu tun haben. functools.partiallöscht einige Metadaten, z __module__. (Außerdem möchte ich für die Aufzeichnung angeben, dass ich sehr erschrecke, wenn ich mir meinen ersten Kommentar zu dieser Antwort ansehe.) Tatsächlich erwähne ich in meiner Frage, dass ich sie bereits verwende, functools.partialaber ich hatte das Gefühl, dass es einen "reineren" Weg geben muss. da es einfach ist, sowohl ungebundene als auch gebundene Methoden zu erhalten.
Dan Passaro

1

Spät zur Party, aber ich kam mit einer ähnlichen Frage hierher: Ich habe eine Klassenmethode und eine Instanz und möchte die Instanz auf die Methode anwenden.

Auf die Gefahr hin, die Frage des OP zu vereinfachen, habe ich etwas weniger Geheimnisvolles getan, das für andere, die hier ankommen, nützlich sein kann (Einschränkung: Ich arbeite in Python 3 - YMMV).

Betrachten Sie diese einfache Klasse:

class Foo(object):

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

    def value(self):
        return self._value

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

Folgendes können Sie damit tun:

>>> meth = Foo.set_value   # the method
>>> a = Foo(12)            # a is an instance with value 12
>>> meth(a, 33)            # apply instance and method
>>> a.value()              # voila - the method was called
33

Dies löst mein Problem nicht - das heißt, ich wollte methaufrufbar sein, ohne ihm das aArgument senden zu müssen (weshalb ich es ursprünglich verwendet habe functools.partial) -, aber dies ist vorzuziehen, wenn Sie die Methode nicht weitergeben müssen und es einfach können Rufen Sie es sofort auf. Auch dies funktioniert in Python 2 genauso wie in Python 3.
Dan Passaro

Entschuldigung, dass Sie Ihre ursprünglichen Anforderungen nicht genauer gelesen haben. Ich bin parteiisch (Wortspiel beabsichtigt) für den Lambda-basierten Ansatz von @ brian-brazil in stackoverflow.com/a/1015355/558639 - er ist ungefähr so ​​rein wie möglich.
fearless_fool
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.