Python: ¿vincular un método independiente?

117

En Python, ¿hay alguna forma de vincular un método independiente sin llamarlo?

Estoy escribiendo un programa wxPython, y para una determinada clase decidí que sería bueno agrupar los datos de todos mis botones como una lista de tuplas a nivel de clase, así:

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)

El problema es que, dado que todos los valores de handlerson métodos independientes, mi programa explota en un resplandor espectacular y lloro.

Estaba buscando en línea una solución a lo que parece ser un problema relativamente sencillo y con solución. Desafortunadamente no pude encontrar nada. En este momento, estoy usando functools.partialpara solucionar esto, pero ¿alguien sabe si hay una forma Pythonic saludable y limpia para vincular un método no vinculado a una instancia y continuar pasándolo sin llamarlo?

Dan Passaro
fuente
6
@Christopher: un método que no está vinculado al alcance del objeto del que fue succionado, por lo que debe pasar self explícitamente.
Aiden Bell
2
Particularmente me gusta "resplandor espectacular y lloro".
jspencer

Respuestas:

174

Todas las funciones también son descriptores , por lo que puede vincularlas llamando a su __get__método:

bound_handler = handler.__get__(self, MyWidget)

Aquí está la excelente guía de descriptores de R. Hettinger .


Como ejemplo autónomo extraído del comentario de Keith :

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
Alex Martelli
fuente
3
Eso es muy bonito. Me gusta cómo puede omitir el tipo y obtener un "método enlazado? .F" en su lugar.
Kiv
Me gusta esta solución sobre la anterior MethodType, porque funciona igual en py3k, mientras que MethodTypelos argumentos de 'se han cambiado un poco.
bgw
10
Y así, una función para vincular funciones a instancias de clase: bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))Ejemplo:class A: pass; a = A(); bind(a, bind, 'bind')
Keith Pinson
2
Eh, aprendes algo nuevo todos los días. @Kazark En Python 3, al menos, también puede omitir el suministro del tipo, ya que __get__lo tomará implícitamente del parámetro del objeto. Ni siquiera estoy seguro de si suministrarlo hace algo, ya que no importa qué tipo proporcione como segundo parámetro, independientemente de cuál sea el primer parámetro. Entonces bind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))debería hacer el truco también. (Aunque bind, personalmente, preferiría tener un usuario utilizable como decorador, pero eso es un asunto diferente).
JAB
1
Vaya, nunca supe que las funciones fueran descriptores. Ese es un diseño muy elegante, los métodos son simplemente funciones en la clase __dict__y el acceso a atributos le brinda métodos no vinculados o vinculados a través del protocolo descriptor normal. Siempre asumí que fue algún tipo de magia que sucedió durantetype.__new__()
JaredL
81

Esto se puede hacer limpiamente con types.MethodType . Ejemplo:

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>>
Kiv
fuente
10
+1 Esto es asombroso, pero no hay ninguna referencia a él en los documentos de Python en la URL que proporcionó.
Kyle Wild
6
+1, prefiero no tener llamadas a funciones mágicas en mi código (es decir __get__). No sé para qué versión de Python probó esto, pero en Python 3.4, la MethodTypefunción toma dos argumentos. La función y la instancia. Entonces esto debería cambiarse a types.MethodType(f, C()).
Dan Milon
¡Aquí está! Es una buena manera de parchear métodos de instancia:wgt.flush = types.MethodType(lambda self: None, wgt)
Ganar y
2
En realidad, se menciona en los documentos, pero en la página del descriptor de la otra respuesta: docs.python.org/3/howto/descriptor.html#functions-and-methods
kai
9

Crear un cierre con self en él no vinculará técnicamente la función, pero es una forma alternativa de resolver el mismo problema subyacente (o muy similar). Aquí tienes un ejemplo trivial:

self.method = (lambda self: lambda args: self.do(args))(self)
Keith Pinson
fuente
1
Sí, esto es aproximadamente lo mismo que mi solución original, que debía usarfunctools.partial(handler, self)
Dan Passaro
7

Esto se unirá selfa handler:

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

Esto funciona pasando selfcomo primer argumento a la función. object.function()es solo azúcar sintáctico para function(object).

brian-brasil
fuente
1
Sí, pero esto llama al método. El problema es que necesito poder pasar el método vinculado como un objeto invocable. Tengo el método sin vincular y la instancia a la que me gustaría estar vinculado, pero no puedo averiguar cómo unirlo todo sin llamarlo inmediatamente
Dan Passaro
9
No, no lo hace, solo llamará al método si lo hace bound_handler (). La definición de una lambda no llama a la lambda.
brian-brazil
1
De hecho, podría usar en functools.partiallugar de definir un lambda. Sin embargo, no resuelve el problema exacto. Todavía estás lidiando con un en functionlugar de un instancemethod.
Alan Plum
5
@Alan: ¿cuál es la diferencia entre un functionprimer argumento cuyo primer argumento editó parcialmente y instancemethod; la escritura de pato no puede ver la diferencia.
Lie Ryan
2
@LieRyan, la diferencia es que todavía no estás tratando con el tipo fundamental. functools.partialdeja caer algunos metadatos, por ejemplo __module__. (También quiero dejar constancia de que me estremezco mucho cuando veo mi primer comentario sobre esta respuesta). De hecho, en mi pregunta, menciono que ya estoy usando, functools.partialpero sentí que tenía que haber una forma "más pura", ya que es fácil obtener métodos vinculados y no vinculados.
Dan Passaro
1

Llegué tarde a la fiesta, pero vine aquí con una pregunta similar: tengo un método de clase y una instancia, y quiero aplicar la instancia al método.

A riesgo de simplificar demasiado la pregunta del OP, terminé haciendo algo menos misterioso que puede ser útil para otros que lleguen aquí (advertencia: estoy trabajando en Python 3 - YMMV).

Considere esta clase simple:

class Foo(object):

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

    def value(self):
        return self._value

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

Esto es lo que puede hacer con él:

>>> 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
intrépido_ tonto
fuente
Esto no resuelve mi problema, que es que quería methser invocable sin tener que enviarle el aargumento (por eso lo usé inicialmente functools.partial), pero esto es preferible si no necesita pasar el método y puede simplemente invocarlo en el acto. Además, esto funciona de la misma manera en Python 2 que en Python 3.
Dan Passaro
Disculpas por no leer tus requisitos originales con más atención. Soy parcial (juego de palabras) con el enfoque basado en lambda dado por @ brian-brazil en stackoverflow.com/a/1015355/558639 : es lo más puro que puedes conseguir.
fearless_fool