Python: Зв'язати незв'язаний метод?


117

У 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цю проблему, але хтось знає, чи існує здоровий, здоровий, пітонічний спосіб прив’язати незв'язаний метод до екземпляра і продовжувати передавати його, не викликаючи його?


6
@Christopher - метод, який не пов'язаний зі сферою об'єкта, з якого він був висмоктаний, тому вам доведеться явно пройти самоврядування.
Ейден Белл

2
Особливо мені подобається "ефектний запал і я плачу".
jspencer

Відповіді:


174

Усі функції також є дескрипторами , тому ви можете зв’язати їх, викликаючи їх __get__метод:

bound_handler = handler.__get__(self, MyWidget)

Ось чудовий посібник Р. Хеттінгера щодо дескрипторів.


Як самодостатній приклад, витягнутий з коментаря Кіта :

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

3
Це досить круто. Мені подобається, як ви можете опустити тип і отримати замість нього "прив'язаний метод? .F".
Ків

Мені подобається це рішення над MethodTypeодним, тому що воно працює однаково в py3k, а MethodTypeаргументи трохи змінені.
bgw

10
Таким чином, функція прив'язує функції до екземплярів класу: bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))Приклад:class A: pass; a = A(); bind(a, bind, 'bind')
Кіт Пінсон

2
Ага, ти щодня дізнаєшся щось нове. @Kazark У Python 3, щонайменше, ви також можете пропустити подачу типу, оскільки це __get__буде неявно від параметра об'єкта. Я навіть не впевнений, чи постачає це щось, оскільки це не має значення, який тип я постачаю як другий параметр незалежно від того, який перший параметр є екземпляром. Так і bind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))слід робити трюк. (Хоча я вважаю за краще bindособисто використовувати його як декоратора, але це інша справа.)
JAB,

1
Нічого собі, ніколи не знали, що функції були дескрипторами. Це дуже елегантний дизайн, методи - це просто звичайні функції класу ', __dict__а доступ до атрибутів дає вам незв'язані або зв'язані методи через звичайний протокол дескриптора. Я завжди припускав, що це якась магія, яка сталася під часtype.__new__()
JaredL

81

Це можна зробити чисто з використанням типів.MethodType . Приклад:

import types

def f(self): print self

class C(object): pass

meth = types.MethodType(f, C(), C) # Bind f to an instance of C
print meth # prints <bound method C.f of <__main__.C object at 0x01255E90>>

10
+1 Це дивовижно, але в документах python за вказаною вами URL-адресою немає посилання на нього.
Кайл Уайлд

6
+1, я вважаю за краще не мати дзвінків до магічних функцій у своєму коді (тобто __get__). Я не знаю, для якої версії python це ви протестували, але на python 3.4 MethodTypeфункція бере два аргументи. Функція та екземпляр. Тому це слід змінити на types.MethodType(f, C()).
Дан Мілон

Ось! Це хороший спосіб виправити примірники методів:wgt.flush = types.MethodType(lambda self: None, wgt)
Winand

2
Він фактично згадується в документах, але на сторінці дескриптора з іншої відповіді: docs.python.org/3/howto/descriptor.html#functions-and-methods
кай

9

Створення замикання з "я" в ньому технічно не пов'язує функцію, але це альтернативний спосіб вирішення тієї ж (або дуже подібної) основної проблеми. Ось банальний приклад:

self.method = (lambda self: lambda args: self.do(args))(self)

1
Так, це приблизно те саме, що і моє оригінальне виправлення, яке потрібно було використатиfunctools.partial(handler, self)
Dan Passaro

7

Це буде пов'язано selfз handler:

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

Це працює, передаючи selfфункцію як перший аргумент. object.function()це просто синтаксичний цукор для function(object).


1
Так, але це викликає метод. Проблема в тому, що мені потрібно вміти передавати зв'язаний метод як об'єкт, що викликається. У мене є незв'язаний метод і екземпляр, який я хотів би, щоб він був пов'язаний, але не можу зрозуміти, як скласти все це без негайного виклику
Dan Passaro

9
Ні, це не так, він викличе метод лише в тому випадку, якщо ви зробите linked_handler (). Визначення лямбда не називає лямбда.
brian-brazil

1
Насправді ви могли використовувати functools.partialзамість визначення лямбда. Однак це не вирішує точну проблему. Ви все ще маєте справу з functionзамість цього instancemethod.
Алан Плюм

5
@Alan: яка різниця між тим, functionчий перший аргумент ви частково редагували, і instancemethod; введення качки не бачить різниці.
Лежать Раян

2
@LieRyan різниця полягає в тому, що ти все ще не маєш справу з фундаментальним типом. functools.partialскидає деякі метадані, наприклад __module__. (Також я хочу заявити, що я записуюсь дуже важко, коли я дивлюсь на перший коментар до цієї відповіді.) Насправді у своєму запитанні я згадую, що вже використовую, functools.partialале мені здалося, що повинен був бути "чистіший" спосіб, оскільки легко отримати як незв'язані, так і зв'язані методи.
Дан Пассаро

1

Пізно до партії, але я прийшов сюди зі схожим питанням: у мене є метод класу та екземпляр, і я хочу застосувати екземпляр до методу.

Загрожуючи спростити питання ОП, я в кінцевому підсумку робив щось менш загадкове, що може бути корисним для інших, хто приїхав сюди (застереження: я працюю в Python 3 - YMMV).

Розглянемо цей простий клас:

class Foo(object):

    def __init__(self, value):
        self._value = value

    def value(self):
        return self._value

    def set_value(self, value):
        self._value = value

Ось що ви можете зробити з цим:

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

Це не вирішує мою проблему - це те, що я хотів methвикликати, не надсилаючи йому aаргумент (саме тому я спочатку використовував functools.partial) - але це краще, якщо вам не потрібно передавати метод і просто викликати його на місці. Також це працює так само, як у Python 2, як у Python 3.
Dan Passaro

Вибачте за те, що не читали ваших початкових вимог ретельніше. Я частково (призначений для каламбуру) до підходу на основі лямбда, який надає @ brian-brazil в stackoverflow.com/a/1015355/558639 - це приблизно так чисто, як ви можете отримати.
безстрашний_фул
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.