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?
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 queMethodType
Los 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. Entoncesbind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))
debería hacer el truco también. (Aunque preferiría tenerbind
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ó durantetype.__new__()
– JaredL
9 de diciembre de 2017 a las 20:08
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, elMethodType
La función toma dos argumentos. La función y la instancia. Así que esto debería cambiarse atypes.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
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
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 ela
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.MethodType
y 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 ela
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
@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