Dalam Python, ada perbedaan antara fungsi dan metode terikat.
>>> def foo():
... print "foo"
...
>>> class A:
... def bar( self ):
... print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>
Metode terikat telah "terikat" (bagaimana deskriptif) ke sebuah instance, dan instance itu akan dilewatkan sebagai argumen pertama setiap kali metode dipanggil.
Callable yang merupakan atribut dari kelas (sebagai lawan dari instance) masih tidak terikat, jadi Anda dapat memodifikasi definisi kelas kapan pun Anda inginkan:
>>> def fooFighters( self ):
... print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters
Instance yang didefinisikan sebelumnya diperbarui juga (selama mereka belum menimpa atribut itu sendiri):
>>> a.fooFighters()
fooFighters
Masalahnya muncul ketika Anda ingin melampirkan metode ke satu contoh:
>>> def barFighters( self ):
... print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)
Fungsi tidak terikat secara otomatis ketika dilampirkan langsung ke instance:
>>> a.barFighters
<function barFighters at 0x00A98EF0>
Untuk mengikatnya, kita bisa menggunakan fungsi MethodType di modul types :
>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters
Kali ini contoh lain dari kelas belum terpengaruh:
>>> a2.barFighters()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'
Informasi lebih lanjut dapat ditemukan dengan membaca tentang deskriptor dan pemrograman metaclass .
MethodType
, aktifkan protokol deskriptor secara manual dan mintalah fungsi menghasilkan instance Anda:barFighters.__get__(a)
menghasilkan metodebarFighters
terikat untuk terikata
.descriptor protocol
vs membuatMethodType
selain mungkin menjadi sedikit lebih mudah dibaca.classmethod
danstaticmethod
deskriptor lainnya juga. Ini menghindari mengacaukan namespace dengan impor lain.a.barFighters = barFighters.__get__(a)
Modul baru sudah ditinggalkan sejak python 2.6 dan dihapus di 3.0, gunakan tipe
lihat http://docs.python.org/library/new.html
Dalam contoh di bawah ini, saya sengaja menghapus nilai balik dari
patch_me()
fungsi. Saya pikir memberikan nilai kembali mungkin membuat orang percaya bahwa patch mengembalikan objek baru, yang tidak benar - itu memodifikasi yang masuk. Mungkin ini dapat memfasilitasi penggunaan monkeypatching yang lebih disiplin.sumber
Pendahuluan - catatan tentang kompatibilitas: jawaban lain hanya dapat bekerja di Python 2 - jawaban ini harus bekerja dengan baik di Python 2 dan 3. Jika menulis Python 3 saja, Anda mungkin meninggalkan pewaris secara eksplisit dari
object
, tetapi jika kode tetap sama .Ya, itu mungkin - Tetapi tidak direkomendasikan
Saya tidak merekomendasikan ini. Ini ide yang buruk. Jangan lakukan itu.
Berikut beberapa alasan:
Jadi, saya sarankan Anda tidak melakukan ini kecuali Anda memiliki alasan yang sangat bagus. Jauh lebih baik untuk mendefinisikan metode yang benar dalam definisi kelas atau kurang disukai untuk menambal monyet kelas secara langsung, seperti ini:
Karena ini bersifat instruktif, saya akan menunjukkan kepada Anda beberapa cara untuk melakukan ini.
Bagaimana itu bisa dilakukan
Ini beberapa kode pengaturan. Kami membutuhkan definisi kelas. Bisa diimpor, tetapi sebenarnya tidak masalah.
Buat sebuah instance:
Buat metode untuk ditambahkan:
Metode sia-sia (0) - gunakan metode deskriptor,
__get__
Pencarian putus-putus pada fungsi memanggil
__get__
metode fungsi dengan instance, mengikat objek ke metode dan dengan demikian menciptakan "metode terikat."dan sekarang:
Metode satu - jenis. Tipe Metode
Pertama, impor jenis, dari mana kita akan mendapatkan konstruktor metode:
Sekarang kita tambahkan metode ke instance. Untuk melakukan ini, kita memerlukan konstruktor MethodType dari
types
modul (yang kita impor di atas).Tanda tangan argumen untuk types. Tipe metode adalah
(function, instance, class)
:dan penggunaan:
Metode dua: pengikatan leksikal
Pertama, kita membuat fungsi wrapper yang mengikat metode ke instance:
pemakaian:
Metode tiga: functools.partial
Fungsi parsial menerapkan argumen pertama pada suatu fungsi (dan argumen kata kunci opsional), dan kemudian dapat dipanggil dengan argumen yang tersisa (dan mengesampingkan argumen kata kunci). Jadi:
Ini masuk akal ketika Anda menganggap bahwa metode terikat adalah fungsi parsial dari instance.
Tidak terikat fungsi sebagai atribut objek - mengapa ini tidak berfungsi:
Jika kita mencoba menambahkan sample_method dengan cara yang sama seperti kita menambahkannya ke kelas, itu tidak terikat dari instance, dan tidak menganggap diri implisit sebagai argumen pertama.
Kita dapat membuat fungsi tidak terikat berfungsi dengan secara eksplisit melewatkan instance (atau apa pun, karena metode ini tidak benar-benar menggunakan
self
variabel argumen), tetapi itu tidak akan konsisten dengan tanda tangan yang diharapkan dari instance lain (jika kita menambal-monyet contoh ini):Kesimpulan
Anda sekarang tahu beberapa cara Anda bisa melakukan ini, tetapi dengan sungguh-sungguh - jangan lakukan ini.
sumber
__get__
Metode juga perlu kelas sebagai parameter berikutnya:sample_method.__get__(foo, Foo)
.Saya pikir jawaban di atas melewatkan poin kunci.
Mari kita kelas dengan metode:
Sekarang, mari kita bermain dengannya di ipython:
Ok, jadi m () entah bagaimana menjadi metode terikat dari A . Tapi benarkah seperti itu?
Ternyata m () hanya fungsi, referensi yang ditambahkan ke A kamus kelas - tidak ada sihir. Lalu mengapa Am memberi kami metode yang tidak terikat? Itu karena titik tidak diterjemahkan ke pencarian kamus sederhana. Secara de facto panggilan dari kelas A.__ __.__ getattribute __ (A, 'm'):
Sekarang, saya tidak yakin dari atas kepala saya mengapa baris terakhir dicetak dua kali, tetapi masih jelas apa yang terjadi di sana.
Sekarang, apa yang dilakukan oleh __getattribute__ default adalah ia memeriksa apakah atributnya disebut descriptor atau tidak, yaitu jika ia mengimplementasikan metode __get__ khusus. Jika mengimplementasikan metode itu, maka apa yang dikembalikan adalah hasil dari memanggil metode __get__ itu. Kembali ke versi pertama dari kelas A kami , inilah yang kami miliki:
Dan karena fungsi Python mengimplementasikan protokol deskriptor, jika mereka dipanggil atas nama objek, mereka mengikat diri ke objek itu dalam metode __get__ mereka.
Oke, jadi bagaimana cara menambahkan metode ke objek yang ada? Dengan asumsi Anda tidak keberatan menambal kelas, itu sesederhana:
Kemudian Bm "menjadi" metode yang tidak terikat, berkat sihir deskriptor.
Dan jika Anda ingin menambahkan metode hanya ke objek tunggal, maka Anda harus meniru mesin sendiri, dengan menggunakan types.MethodType:
Ngomong-ngomong:
sumber
Di Python, tambalan monyet biasanya berfungsi dengan menimpa kelas atau fungsi dengan tanda tangan Anda. Di bawah ini adalah contoh dari Zope Wiki :
Kode itu akan menimpa / membuat metode yang disebut berbicara di kelas. Dalam posting terbaru Jeff Atwood tentang penambalan monyet . Dia menunjukkan contoh dalam C # 3.0 yang merupakan bahasa saat ini yang saya gunakan untuk bekerja.
sumber
Anda bisa menggunakan lambda untuk mengikat metode ke instance:
Keluaran:
sumber
Setidaknya ada dua cara untuk melampirkan metode ke instance tanpa
types.MethodType
:1:
2:
Tautan yang berguna:
model data - memanggil deskriptor
Descriptor Panduan HowTo - memanggil deskriptor
sumber
Apa yang Anda cari adalah
setattr
saya percaya. Gunakan ini untuk mengatur atribut pada objek.sumber
A
, bukan instancea
.setattr(A,'printme',printme)
bukan hanyaA.printme = printme
?Karena pertanyaan ini menanyakan versi non-Python, inilah JavaScript:
sumber
Mengkonsolidasikan jawaban Jason Pratt dan komunitas wiki, dengan melihat hasil dari berbagai metode pengikatan:
Terutama perhatikan bagaimana menambahkan fungsi penjilidan sebagai metode kelas berfungsi , tetapi cakupan referensi salah.
Secara pribadi, saya lebih suka rute fungsi ADDMETHOD eksternal, karena memungkinkan saya untuk secara dinamis menetapkan nama metode baru di dalam iterator juga.
sumber
addmethod
ditulis ulang dengan cara berikutdef addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )
memecahkan masalahIni sebenarnya merupakan tambahan untuk jawaban "Jason Pratt"
Meskipun jawaban Jason berhasil, itu hanya berfungsi jika seseorang ingin menambahkan fungsi ke kelas. Itu tidak berfungsi untuk saya ketika saya mencoba memuat ulang metode yang sudah ada dari file kode sumber .py.
Butuh waktu lama bagi saya untuk menemukan solusi, tetapi triknya tampak sederhana ... 1. mengimpor kode dari file kode sumber 2.dan memaksa memuat ulang jenis penggunaan yang ketiga. FunctionType (...) untuk mengonversi Metode yang diimpor dan terikat ke fungsi Anda juga dapat meneruskan variabel global saat ini, karena metode yang dimuat ulang akan berada dalam ruang nama yang berbeda 4. Sekarang Anda dapat melanjutkan seperti yang disarankan oleh "Jason Pratt" menggunakan types. Methype ... )
Contoh:
sumber
Jika itu bisa membantu, saya baru-baru ini merilis perpustakaan Python bernama Gorilla untuk membuat proses penambalan monyet lebih nyaman.
Menggunakan fungsi
needle()
untuk menambal modul bernamaguineapig
berjalan sebagai berikut:Tetapi juga menangani kasus penggunaan yang lebih menarik seperti yang ditunjukkan dalam FAQ dari dokumentasi .
Kode ini tersedia di GitHub .
sumber
Pertanyaan ini dibuka bertahun-tahun yang lalu, tapi hei, ada cara mudah untuk mensimulasikan pengikatan fungsi ke instance kelas menggunakan dekorator:
Di sana, ketika Anda meneruskan fungsi dan instance ke pengikat binder, itu akan membuat fungsi baru, dengan objek kode yang sama dengan yang pertama. Kemudian, instance kelas yang diberikan disimpan dalam atribut fungsi yang baru dibuat. Dekorator mengembalikan fungsi (ketiga) yang memanggil fungsi yang disalin secara otomatis, memberikan instance sebagai parameter pertama.
Kesimpulannya Anda mendapatkan fungsi mensimulasikan mengikat ke instance kelas. Membiarkan fungsi asli tidak berubah.
sumber
Apa yang diposting Jason Pratt benar.
Seperti yang Anda lihat, Python tidak menganggap b () berbeda dari a (). Dalam Python semua metode hanyalah variabel yang merupakan fungsi.
sumber
Test
, bukan turunannya.Saya merasa aneh bahwa tidak ada yang menyebutkan bahwa semua metode yang tercantum di atas membuat referensi siklus antara metode yang ditambahkan dan instance, menyebabkan objek menjadi persisten hingga pengumpulan sampah. Ada trik lama menambahkan deskripsi dengan memperluas kelas objek:
sumber
Dengan ini, Anda dapat menggunakan penunjuk diri
sumber