Python: Bind an Unbound Method?

117

Di Python, apakah ada cara untuk mengikat metode tidak terikat tanpa memanggilnya?

Saya menulis program wxPython, dan untuk kelas tertentu saya memutuskan akan lebih baik untuk mengelompokkan data dari semua tombol saya bersama-sama sebagai daftar tupel tingkat kelas, seperti:

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)

Masalahnya adalah, karena semua nilai dari handlermetode tidak terikat, program saya meledak dalam kobaran api yang spektakuler dan saya menangis.

Saya sedang mencari solusi online untuk apa yang tampaknya merupakan masalah yang relatif mudah dan dapat dipecahkan. Sayangnya saya tidak dapat menemukan apa pun. Saat ini, saya menggunakan functools.partialuntuk mengatasi ini, tetapi apakah ada yang tahu jika ada cara Pythonic yang bersih, sehat, untuk mengikat metode tidak terikat ke sebuah instance dan terus menyebarkannya tanpa memanggilnya?

Dan Passaro
sumber
6
@Christopher - Sebuah metode yang tidak terikat pada ruang lingkup objek yang disedotnya, jadi Anda harus melewatkan diri secara eksplisit.
Aiden Bell
2
Saya khususnya menyukai "kobaran api yang spektakuler dan saya menangis".
jspencer

Jawaban:

174

Semua fungsi juga merupakan deskriptor , sehingga Anda dapat mengikatnya dengan memanggil __get__metode mereka :

bound_handler = handler.__get__(self, MyWidget)

Berikut panduan bagus dari R. Hettinger untuk deskriptor.


Sebagai contoh mandiri ditarik dari komentar Keith :

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
Alex Martelli
sumber
3
Itu sangat keren. Saya suka bagaimana Anda bisa menghilangkan tipe dan mendapatkan kembali "metode terikat? .F" sebagai gantinya.
Kiv
Saya suka solusi ini daripada yang MethodTypesatu, karena bekerja sama di py3k, sementara MethodTypeargumen telah diubah sedikit.
bgw
10
Dan dengan demikian, fungsi untuk mengikat fungsi ke instance kelas: bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))Contoh:class A: pass; a = A(); bind(a, bind, 'bind')
Keith Pinson
2
Huh, Anda belajar sesuatu yang baru setiap hari. @Kazark Dalam Python 3, setidaknya, Anda juga dapat melewati penyediaan tipe, karena __get__akan mengambilnya secara implisit dari parameter objek. Saya bahkan tidak yakin apakah memasok itu melakukan apa-apa, karena tidak ada bedanya tipe apa yang saya berikan sebagai parameter kedua terlepas dari apa parameter pertama adalah turunannya. Jadi bind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))sebaiknya lakukan triknya juga. (Meskipun saya lebih suka memiliki yang binddapat digunakan sebagai dekorator, secara pribadi, tetapi itu masalah yang berbeda.)
JAB
1
Wow, tidak pernah tahu fungsi adalah deskriptor. Itu adalah desain yang sangat elegan, metode hanyalah fungsi biasa di kelas ' __dict__dan akses atribut memberi Anda metode tak terikat atau terikat melalui protokol deskriptor normal. Saya selalu berasumsi bahwa itu adalah semacam keajaiban yang terjadi selamatype.__new__()
JaredL
81

Ini bisa dilakukan dengan rapi dengan types.MethodType . Contoh:

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>>
Kiv
sumber
10
+1 Ini luar biasa, tetapi tidak ada referensi tentangnya di dokumen python di URL yang Anda berikan.
Kyle Wild
6
+1, Saya memilih untuk tidak memiliki panggilan ke fungsi ajaib dalam kode saya (yaitu __get__). Saya tidak tahu versi python mana yang Anda uji ini, tetapi pada python 3.4, MethodTypefungsinya membutuhkan dua argumen. Fungsi dan contoh. Jadi ini harus diubah menjadi types.MethodType(f, C()).
Dan Milon
Ini dia! Ini adalah cara yang baik untuk menambal metode contoh:wgt.flush = types.MethodType(lambda self: None, wgt)
Winand
2
Ini sebenarnya disebutkan di dokumen, tetapi di halaman deskriptor dari jawaban lain: docs.python.org/3/howto/descriptor.html#functions-and-methods
kai
9

Membuat penutupan dengan self di dalamnya tidak akan mengikat fungsi secara teknis, tetapi merupakan cara alternatif untuk memecahkan masalah mendasar yang sama (atau sangat mirip). Berikut contoh sepele:

self.method = (lambda self: lambda args: self.do(args))(self)
Keith Pinson
sumber
1
Ya, ini hampir sama dengan perbaikan asli saya, yang akan digunakanfunctools.partial(handler, self)
Dan Passaro
7

Ini akan mengikat selfke handler:

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

Ini bekerja dengan meneruskan selfsebagai argumen pertama ke fungsi tersebut. object.function()hanyalah gula sintaksis untuk function(object).

brian-brazil
sumber
1
Ya, tapi ini memanggil metode tersebut. Masalahnya adalah saya harus bisa melewatkan metode terikat sebagai objek yang dapat dipanggil. Saya memiliki metode tidak terikat dan contoh yang saya inginkan untuk mengikatnya, tetapi tidak tahu bagaimana menggabungkan semuanya tanpa segera memanggilnya
Dan Passaro
9
Tidak, itu hanya akan memanggil metode jika Anda melakukan bound_handler (). Mendefinisikan lambda tidak memanggil lambda.
brian-brazil
1
Anda sebenarnya dapat menggunakan functools.partialalih-alih menentukan lambda. Itu tidak menyelesaikan masalah yang sebenarnya. Anda masih berurusan dengan a, functionbukan instancemethod.
Alan Plum
5
@Alan: apa perbedaan antara functionyang argumen pertama Anda sebagian-ed dan instancemethod; mengetik bebek tidak bisa melihat perbedaannya.
Lie Ryan
2
@LieRyan perbedaannya adalah Anda masih belum berurusan dengan tipe fundamental. functools.partialmenjatuhkan beberapa metadata, misalnya __module__. (Juga saya ingin menyatakan untuk catatan bahwa saya merasa ngeri saat melihat komentar pertama saya pada jawaban ini.) Sebenarnya dalam pertanyaan saya, saya menyebutkan bahwa saya sudah menggunakan functools.partialtetapi saya merasa harus ada cara yang "lebih murni", karena mudah untuk mendapatkan metode tak terikat dan terikat.
Dan Passaro
1

Terlambat ke pesta, tetapi saya datang ke sini dengan pertanyaan serupa: Saya memiliki metode kelas dan contoh, dan ingin menerapkan contoh ke metode.

Dengan risiko menyederhanakan pertanyaan OP, saya akhirnya melakukan sesuatu yang tidak terlalu misterius yang mungkin berguna bagi orang lain yang tiba di sini (peringatan: Saya bekerja dengan Python 3 - YMMV).

Pertimbangkan kelas sederhana ini:

class Foo(object):

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

    def value(self):
        return self._value

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

Inilah yang dapat Anda lakukan dengannya:

>>> 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
fearless_fool
sumber
Ini tidak menyelesaikan masalah saya - yaitu saya ingin tidak methdapat dipakai tanpa harus mengirimkan aargumen (itulah sebabnya saya awalnya menggunakan functools.partial) - tetapi ini lebih disukai jika Anda tidak perlu meneruskan metode ini dan bisa saja memintanya di tempat. Ini juga bekerja dengan cara yang sama di Python 2 seperti di Python 3.
Dan Passaro
Maaf karena tidak membaca persyaratan asli Anda dengan lebih cermat. Saya parsial (pun intended) ke pendekatan berbasis lambda yang diberikan oleh @ brian-brazil di stackoverflow.com/a/1015355/558639 - ini semurni yang Anda bisa dapatkan.
fearless_fool