¿Cómo enlazar un método no enlazado sin llamarlo?

7 minutos de lectura

Avatar de usuario de Dan Passaro
Dan Passaro

En Python, ¿hay alguna forma de vincular un método no vinculado 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 handler son métodos desatados, mi programa estalla en un estallido espectacular y lloro.

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

  • @Christopher: un método que no está vinculado al alcance del objeto del que se extrajo, por lo que debe pasarlo explícitamente.

    – Aiden Bell

    18 de junio de 2009 a las 21:42

  • Me gusta especialmente “Espectacular resplandor y lloro”.

    – jspencer

    27 de junio de 2018 a las 19:57

Avatar de usuario de Alex Martelli
alex martelli

Todas las funciones también descriptorespor lo que puede vincularlos llamando a su __get__ método:

bound_handler = handler.__get__(self, MyWidget)

Aquí está el excelente trabajo de R. Hettinger guía a los descriptores.


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

  • Eso es muy bonito. Me gusta cómo puede omitir el tipo y obtener un “método enlazado? .f” en su lugar.

    – Kiv

    18 de junio de 2009 a las 22:02

  • Me gusta esta solución sobre el MethodType one, porque funciona igual en py3k, mientras que MethodTypeLos argumentos de han cambiado un poco.

    – bgw

    8 de marzo de 2011 a las 1:27

  • Y por lo tanto, 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

    13 de agosto de 2011 a las 16:53


  • Huh, 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 proporcionarlo hace algo, ya que no importa qué tipo proporcione como segundo parámetro, independientemente de cuál sea una instancia del 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 preferiría tener bind utilizable como decorador, personalmente, pero eso es un asunto diferente).

    – JAB

    28/01/2014 a las 21:01


  • Wow, nunca supe que las funciones eran descriptores. Ese es un diseño muy elegante, los métodos son simplemente funciones en la clase’ __dict__ y el acceso de atributos le brinda métodos no vinculados o vinculados a través del protocolo de descriptor normal. Siempre supuse que era algún tipo de magia que sucedió durante type.__new__()

    – JaredL

    9 de diciembre de 2017 a las 20:08

Avatar de usuario de Kiv
Kiv

Esto se puede hacer con types.MethodType:

import types

bound_handler = types.MethodType(handler, self)

  • +1 Esto es increíble, pero no hay ninguna referencia a él en los documentos de Python en la URL que proporcionó.

    – Kyle Salvaje

    23 de diciembre de 2011 a las 13:30

  • +1, prefiero no tener llamadas a funciones mágicas en mi código (es decir, __get__). No sé para qué versión de python probaste esto, pero en python 3.4, el MethodType La función toma dos argumentos. La función y la instancia. Así que esto debería cambiarse a types.MethodType(f, C()).

    – Dan Milón

    8 oct 2014 a las 12:48

  • ¡Aquí lo tienes! Es una buena forma de parchear métodos de instancia: wgt.flush = types.MethodType(lambda self: None, wgt)

    – Winand

    31 de julio de 2015 a las 7:35

  • En realidad, se menciona en los documentos, pero en la página de descripción de la otra respuesta: docs.python.org/3/howto/descriptor.html#funciones-y-métodos

    – kai

    8 de agosto de 2018 a las 11:27

Avatar de usuario de Keith Pinson
keith pinson

Con un cierretambién conocido como un expresión cerrada (a diferencia de un expresión abierta), que es una expresión sin variables libres:

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

Aquí handler y self son variables libres en la expresión lambda interna y variables ligadas en la expresión lambda exterior, y args y kwargs son variables vinculadas en las expresiones lambda interna y externa, por lo que la expresión lambda externa es un cierre.

  • Sí, esto es casi lo mismo que mi arreglo original, que era usar functools.partial(handler, self)

    – Dan Passaro

    27 de julio de 2018 a las 21:41

avatar de usuario de brian-brazil
brian-brasil

esto se unirá self a handler:

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

Esto funciona pasando self como primer argumento de la función. obj.f() es solo azúcar sintáctico para f(obj).

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 llegan 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 puedes 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

  • Esto no resuelve mi problema, que es lo que quería meth ser invocable sin tener que enviarle el a argumento (razón por la cual inicialmente usé functools.partial), pero esto es preferible si no necesita pasar el método y puede invocarlo en el acto. Además, esto funciona de la misma manera en Python 2 que en Python 3.

    – Dan Passaro

    22 de julio de 2018 a las 17:27


  • Disculpas por no leer tus requisitos originales con más cuidado. Soy parcial (juego de palabras) con el enfoque basado en lambda proporcionado por @brian-brazil en stackoverflow.com/a/1015355/558639: es lo más puro posible.

    – sin miedo_tonto

    22 de julio de 2018 a las 22:48


Para ampliar la respuesta de @Keith Pinson que usa un cierre y la respuesta de @brian-brazil que no lo hace, he aquí por qué la primera es correcta.

Ejemplo con cierre:

def handler(self, *args, **kwargs):
    return self


self = True
bound_handler = (lambda handler, self:
    lambda *args, **kwargs: handler(self, *args, **kwargs)
)(handler, self)
bound_handler()  # returns True
self = False
bound_handler()  # returns True
handler = print
bound_handler()  # returns True

La expresión lambda externa está cerrada, por lo que sus variables vinculadas handler y self no se puede rebotar. __get__, types.MethodTypey functools.partial tener el mismo comportamiento.

Ejemplo sin cierre:

def handler(self, *args, **kwargs):
    return self


self = True
bound_handler = lambda *args, **kwargs: handler(self, *args, **kwargs)
bound_handler()  # returns True
self = False
bound_handler()  # returns False
handler = print
bound_handler()  # prints False

La expresión lambda está abierta por lo que sus variables libres handler y self puede ser rebote.

  • Esto no resuelve mi problema, que es lo que quería meth ser invocable sin tener que enviarle el a argumento (razón por la cual inicialmente usé functools.partial), pero esto es preferible si no necesita pasar el método y puede invocarlo en el acto. Además, esto funciona de la misma manera en Python 2 que en Python 3.

    – Dan Passaro

    22 de julio de 2018 a las 17:27


  • Disculpas por no leer tus requisitos originales con más cuidado. Soy parcial (juego de palabras) con el enfoque basado en lambda proporcionado por @brian-brazil en stackoverflow.com/a/1015355/558639: es lo más puro posible.

    – sin miedo_tonto

    22 de julio de 2018 a las 22:48


¿Ha sido útil esta solución?