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.