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 handler
son 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.partial
para 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?
Respuestas:
Todas las funciones también son descriptores , por lo que puede vincularlas llamando a su
__get__
método:Aquí está la excelente guía de descriptores de R. Hettinger .
Como ejemplo autónomo extraído del comentario de Keith :
fuente
MethodType
, porque funciona igual en py3k, mientras queMethodType
los argumentos de 'se han cambiado un poco.bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))
Ejemplo:class A: pass;
a = A();
bind(a, bind, 'bind')
__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. Entoncesbind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))
debería hacer el truco también. (Aunquebind
, personalmente, preferiría tener un usuario utilizable como decorador, pero eso es un asunto diferente).__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__()
Esto se puede hacer limpiamente con types.MethodType . Ejemplo:
fuente
__get__
). No sé para qué versión de Python probó esto, pero en Python 3.4, laMethodType
función toma dos argumentos. La función y la instancia. Entonces esto debería cambiarse atypes.MethodType(f, C())
.wgt.flush = types.MethodType(lambda self: None, wgt)
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:
fuente
functools.partial(handler, self)
Esto se unirá
self
ahandler
:Esto funciona pasando
self
como primer argumento a la función.object.function()
es solo azúcar sintáctico parafunction(object)
.fuente
functools.partial
lugar de definir un lambda. Sin embargo, no resuelve el problema exacto. Todavía estás lidiando con un enfunction
lugar de uninstancemethod
.function
primer argumento cuyo primer argumento editó parcialmente yinstancemethod
; la escritura de pato no puede ver la diferencia.functools.partial
deja 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.partial
pero sentí que tenía que haber una forma "más pura", ya que es fácil obtener métodos vinculados y no vinculados.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:
Esto es lo que puede hacer con él:
fuente
meth
ser invocable sin tener que enviarle ela
argumento (por eso lo usé inicialmentefunctools.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.