В Python есть ли способ привязать несвязанный метод без его вызова?
Я пишу программу wxPython, и для определенного класса я решил, что было бы неплохо сгруппировать данные всех моих кнопок вместе в виде списка кортежей на уровне класса, например:
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)
Проблема в том, что поскольку все значения handler
являются несвязанными методами, моя программа взрывается ярким пламенем, и я плачу.
Я искал в Интернете решение, которое, казалось, должно быть относительно простой и решаемой проблемой. К сожалению, я ничего не нашел. Прямо сейчас я использую, functools.partial
чтобы обойти это, но знает ли кто-нибудь, есть ли чистый, здоровый, питонический способ привязать несвязанный метод к экземпляру и продолжать передавать его, не вызывая его?
Ответы:
Все функции также являются дескрипторами , поэтому вы можете связать их, вызвав их
__get__
метод:Вот отличное руководство по дескрипторам Р. Хеттингера .
В качестве автономного примера, взятого из комментария Кита :
источник
MethodType
одно, потому что оно работает так же в py3k, аMethodType
аргументы были немного изменены.bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))
Пример:class A: pass;
a = A();
bind(a, bind, 'bind')
__get__
будет неявно брать из параметра объекта. Я даже не уверен, что это что-то дает, так как не имеет значения, какой тип я использую в качестве второго параметра, независимо от того, экземпляром какого первого параметра является. Такbind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))
что тоже должно сработать. (Хотяbind
лично я бы предпочел, чтобы его можно было использовать в качестве декоратора, но это другой вопрос.)__dict__
а доступ к атрибутам дает вам несвязанные или связанные методы через обычный протокол дескриптора. Я всегда предполагал , что это была какая - то магия , что произошло во времяtype.__new__()
Это можно сделать чисто с помощью types.MethodType . Пример:
источник
__get__
) не было вызовов магических функций . Я не знаю, для какой версии python вы это тестировали, но на python 3.4MethodType
функция принимает два аргумента. Функция и экземпляр. Так что это следует изменить наtypes.MethodType(f, C())
.wgt.flush = types.MethodType(lambda self: None, wgt)
Создание замыкания с самим собой не будет технически связывать функцию, но это альтернативный способ решения той же (или очень похожей) основной проблемы. Вот банальный пример:
источник
functools.partial(handler, self)
Это будет привязано
self
кhandler
:Это работает путем передачи
self
функции в качестве первого аргумента.object.function()
просто синтаксический сахар дляfunction(object)
.источник
functools.partial
вместо определения лямбда. Однако это не решает точную проблему. Вы по-прежнему имеете дело с afunction
вместоinstancemethod
.function
чей первый аргумент вы частично определили, иinstancemethod
; утка печатая не вижу разницы.functools.partial
отбрасывает некоторые метаданные, например__module__
. (Также я хочу заявить для записи, что я очень сильно съеживаюсь, когда смотрю на свой первый комментарий к этому ответу.) На самом деле, в моем вопросе я упоминаю, что уже использую,functools.partial
но я чувствовал, что должен быть "более чистый" способ, поскольку легко получить как несвязанные, так и связанные методы.Поздно к вечеринке, но я пришел сюда с аналогичным вопросом: у меня есть метод класса и экземпляр, и я хочу применить этот экземпляр к методу.
Рискуя чрезмерно упростить вопрос OP, я закончил тем, что сделал что-то менее загадочное, что может быть полезно для других, которые прибывают сюда (предостережение: я работаю в Python 3 - YMMV).
Рассмотрим этот простой класс:
Вот что вы можете с этим сделать:
источник
meth
чтобы меня можно было вызывать, не отправляя емуa
аргумент (именно поэтому я изначально использовалfunctools.partial
) - но это предпочтительнее, если вам не нужно передавать метод и можно просто вызвать его на месте. Также это работает в Python 2 так же, как и в Python 3.