Em Python, existe uma maneira de vincular um método não vinculado sem chamá-lo?
Estou escrevendo um programa wxPython e, para uma determinada classe, decidi que seria bom agrupar os dados de todos os meus botões como uma lista de tuplas em nível de classe, assim:
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)
O problema é que, como todos os valores de handler
são métodos desvinculados, meu programa explode em um incêndio espetacular e eu choro.
Eu estava procurando online por uma solução para o que parece ser um problema relativamente simples e solucionável. Infelizmente não consegui encontrar nada. No momento, estou usando functools.partial
para contornar isso, mas alguém sabe se existe uma maneira Pythônica com sensação de limpeza, saudável, de vincular um método não acoplado a uma instância e continuar passando-o sem chamá-lo?
Respostas:
Todas as funções também são descritores , portanto, você pode vinculá-las chamando seu
__get__
método:Aqui está o excelente guia de descritores de R. Hettinger .
Como um exemplo independente retirado do comentário de Keith :
fonte
MethodType
outra, porque ela funciona da mesma forma em py3k, enquantoMethodType
os argumentos de foram um pouco alterados.bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))
Exemplo:class A: pass;
a = A();
bind(a, bind, 'bind')
__get__
irá tirar isso implicitamente do parâmetro do objeto. Eu nem tenho certeza se fornecê-lo faz alguma coisa, já que não faz diferença o tipo que eu forneço como o segundo parâmetro, independentemente do que o primeiro parâmetro é uma instância. Então,bind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))
deve fazer o truque também. (Embora eu prefirabind
utilizá-lo como decorador, pessoalmente, mas isso é uma questão diferente.)__dict__
e o acesso ao atributo fornece métodos não acoplados ou vinculados por meio do protocolo descritor normal. Sempre achei que era algum tipo de mágica que aconteceu durantetype.__new__()
Isso pode ser feito de forma limpa com types.MethodType . Exemplo:
fonte
__get__
). Não sei em qual versão do python você testou, mas no python 3.4, aMethodType
função leva dois argumentos. A função e a instância. Portanto, deve ser alterado paratypes.MethodType(f, C())
.wgt.flush = types.MethodType(lambda self: None, wgt)
Criar um fechamento com self nele não vinculará tecnicamente a função, mas é uma maneira alternativa de resolver o mesmo (ou muito semelhante) problema subjacente. Aqui está um exemplo trivial:
fonte
functools.partial(handler, self)
Isso se ligará
self
ahandler
:Isso funciona passando
self
como o primeiro argumento para a função.object.function()
é apenas açúcar sintático parafunction(object)
.fonte
functools.partial
vez de definir um lambda. Porém, isso não resolve exatamente o problema. Você ainda está lidando com um emfunction
vez de uminstancemethod
.function
cujo primeiro argumento você parcial-ed einstancemethod
; a digitação de pato não consegue ver a diferença.functools.partial
descarta alguns metadados, por exemplo__module__
. (Também quero deixar registrado que me encolho muito quando vejo meu primeiro comentário sobre esta resposta.) Na verdade, em minha pergunta, mencionei que já estou usando,functools.partial
mas senti que deveria haver uma maneira "mais pura", uma vez que é fácil obter métodos não associados e associados.Atrasado para a festa, mas cheguei aqui com uma pergunta semelhante: Tenho um método de classe e uma instância e quero aplicar a instância ao método.
Correndo o risco de simplificar demais a pergunta do OP, acabei fazendo algo menos misterioso que pode ser útil para outras pessoas que chegam aqui (ressalva: estou trabalhando em Python 3 - YMMV).
Considere esta classe simples:
Aqui está o que você pode fazer com ele:
fonte
meth
ser invokable sem ter que enviar oa
argumento (que é o motivo pelo qual usei inicialmentefunctools.partial
) - mas isso é preferível se você não precisar passar o método e puder apenas invocá-lo no local. Além disso, isso funciona da mesma maneira no Python 2 e no Python 3.