Jawaban:
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 .
descriptor protocol
vs membuat MethodType
selain mungkin menjadi sedikit lebih mudah dibaca.
classmethod
dan staticmethod
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.
import types
class A(object):#but seems to work for old style objects too
pass
def patch_me(target):
def method(target,x):
print "x=",x
print "called from", target
target.method = types.MethodType(method,target)
#add more if needed
a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>
patch_me(a) #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6) #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>
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 .
Menambahkan Metode ke Instance Obyek yang Ada
Saya telah membaca bahwa adalah mungkin untuk menambahkan metode ke objek yang ada (misalnya tidak dalam definisi kelas) dengan Python.
Saya mengerti bahwa itu tidak selalu merupakan keputusan yang baik untuk melakukannya. Tapi, bagaimana mungkin orang melakukan ini?
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:
Foo.sample_method = sample_method
Karena ini bersifat instruktif, saya akan menunjukkan kepada Anda beberapa cara untuk melakukan ini.
Ini beberapa kode pengaturan. Kami membutuhkan definisi kelas. Bisa diimpor, tetapi sebenarnya tidak masalah.
class Foo(object):
'''An empty class to demonstrate adding a method to an instance'''
Buat sebuah instance:
foo = Foo()
Buat metode untuk ditambahkan:
def sample_method(self, bar, baz):
print(bar + baz)
__get__
Pencarian putus-putus pada fungsi memanggil __get__
metode fungsi dengan instance, mengikat objek ke metode dan dengan demikian menciptakan "metode terikat."
foo.sample_method = sample_method.__get__(foo)
dan sekarang:
>>> foo.sample_method(1,2)
3
Pertama, impor jenis, dari mana kita akan mendapatkan konstruktor metode:
import types
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)
:
foo.sample_method = types.MethodType(sample_method, foo, Foo)
dan penggunaan:
>>> foo.sample_method(1,2)
3
Pertama, kita membuat fungsi wrapper yang mengikat metode ke instance:
def bind(instance, method):
def binding_scope_fn(*args, **kwargs):
return method(instance, *args, **kwargs)
return binding_scope_fn
pemakaian:
>>> foo.sample_method = bind(foo, sample_method)
>>> foo.sample_method(1,2)
3
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:
>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3
Ini masuk akal ketika Anda menganggap bahwa metode terikat adalah fungsi parsial dari instance.
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.
>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)
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):
>>> foo.sample_method(foo, 1, 2)
3
Anda sekarang tahu beberapa cara Anda bisa melakukan ini, tetapi dengan sungguh-sungguh - jangan lakukan ini.
__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:
class A(object):
def m(self):
pass
Sekarang, mari kita bermain dengannya di ipython:
In [2]: A.m
Out[2]: <unbound method A.m>
Ok, jadi m () entah bagaimana menjadi metode terikat dari A . Tapi benarkah seperti itu?
In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>
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'):
In [11]: class MetaA(type):
....: def __getattribute__(self, attr_name):
....: print str(self), '-', attr_name
In [12]: class A(object):
....: __metaclass__ = MetaA
In [23]: A.m
<class '__main__.A'> - m
<class '__main__.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:
In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>
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:
B.m = m
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:
b.m = types.MethodType(m, b)
Ngomong-ngomong:
In [2]: A.m
Out[2]: <unbound method A.m>
In [59]: type(A.m)
Out[59]: <type 'instancemethod'>
In [60]: type(b.m)
Out[60]: <type 'instancemethod'>
In [61]: types.MethodType
Out[61]: <type 'instancemethod'>
Di Python, tambalan monyet biasanya berfungsi dengan menimpa kelas atau fungsi dengan tanda tangan Anda. Di bawah ini adalah contoh dari Zope Wiki :
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
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.
Anda bisa menggunakan lambda untuk mengikat metode ke instance:
def run(self):
print self._instanceString
class A(object):
def __init__(self):
self._instanceString = "This is instance string"
a = A()
a.run = lambda: run(a)
a.run()
Keluaran:
This is instance string
Setidaknya ada dua cara untuk melampirkan metode ke instance tanpa types.MethodType
:
>>> class A:
... def m(self):
... print 'im m, invoked with: ', self
>>> a = A()
>>> a.m()
im m, invoked with: <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>>
>>> def foo(firstargument):
... print 'im foo, invoked with: ', firstargument
>>> foo
<function foo at 0x978548c>
1:
>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with: <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>
2:
>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with: <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>
Tautan yang berguna:
model data - memanggil deskriptor
Descriptor Panduan HowTo - memanggil deskriptor
Apa yang Anda cari adalah setattr
saya percaya. Gunakan ini untuk mengatur atribut pada objek.
>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>
A
, bukan instance a
.
setattr(A,'printme',printme)
bukan hanya A.printme = printme
?
Karena pertanyaan ini menanyakan versi non-Python, inilah JavaScript:
a.methodname = function () { console.log("Yay, a new method!") }
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.
#!/usr/bin/python -u
import types
import inspect
## dynamically adding methods to a unique instance of a class
# get a list of a class's method type attributes
def listattr(c):
for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
print m[0], m[1]
# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
c.__dict__[name] = types.MethodType(method, c)
class C():
r = 10 # class attribute variable to test bound scope
def __init__(self):
pass
#internally bind a function as a method of self's class -- note that this one has issues!
def addmethod(self, method, name):
self.__dict__[name] = types.MethodType( method, self.__class__ )
# predfined function to compare with
def f0(self, x):
print 'f0\tx = %d\tr = %d' % ( x, self.r)
a = C() # created before modified instnace
b = C() # modified instnace
def f1(self, x): # bind internally
print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
print 'f4\tx = %d\tr = %d' % ( x, self.r )
b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')
b.f0(0) # OUT: f0 x = 0 r = 10
b.f1(1) # OUT: f1 x = 1 r = 10
b.f2(2) # OUT: f2 x = 2 r = 10
b.f3(3) # OUT: f3 x = 3 r = 10
b.f4(4) # OUT: f4 x = 4 r = 10
k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)
b.f0(0) # OUT: f0 x = 0 r = 2
b.f1(1) # OUT: f1 x = 1 r = 10 !!!!!!!!!
b.f2(2) # OUT: f2 x = 2 r = 2
b.f3(3) # OUT: f3 x = 3 r = 2
b.f4(4) # OUT: f4 x = 4 r = 2
c = C() # created after modifying instance
# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>
print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>
print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>
Secara pribadi, saya lebih suka rute fungsi ADDMETHOD eksternal, karena memungkinkan saya untuk secara dinamis menetapkan nama metode baru di dalam iterator juga.
def y(self, x):
pass
d = C()
for i in range(1,5):
ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
addmethod
ditulis ulang dengan cara berikut def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )
memecahkan masalah
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:
# this class resides inside ReloadCodeDemo.py
class A:
def bar( self ):
print "bar1"
def reloadCode(self, methodName):
''' use this function to reload any function of class A'''
import types
import ReloadCodeDemo as ReloadMod # import the code as module
reload (ReloadMod) # force a reload of the module
myM = getattr(ReloadMod.A,methodName) #get reloaded Method
myTempFunc = types.FunctionType(# convert the method to a simple function
myM.im_func.func_code, #the methods code
globals(), # globals to use
argdefs=myM.im_func.func_defaults # default values for variables if any
)
myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
setattr(self,methodName,myNewM) # add the method to the function
if __name__ == '__main__':
a = A()
a.bar()
# now change your code and save the file
a.reloadCode('bar') # reloads the file
a.bar() # now executes the reloaded code
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 bernama guineapig
berjalan sebagai berikut:
import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
print("awesome")
Tetapi juga menangani kasus penggunaan yang lebih menarik seperti yang ditunjukkan dalam FAQ dari dokumentasi .
Kode ini tersedia di GitHub .
Pertanyaan ini dibuka bertahun-tahun yang lalu, tapi hei, ada cara mudah untuk mensimulasikan pengikatan fungsi ke instance kelas menggunakan dekorator:
def binder (function, instance):
copy_of_function = type (function) (function.func_code, {})
copy_of_function.__bind_to__ = instance
def bound_function (*args, **kwargs):
return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
return bound_function
class SupaClass (object):
def __init__ (self):
self.supaAttribute = 42
def new_method (self):
print self.supaAttribute
supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)
otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)
otherInstance.supMethod ()
supaInstance.supMethod ()
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.
Apa yang diposting Jason Pratt benar.
>>> class Test(object):
... def a(self):
... pass
...
>>> def b(self):
... pass
...
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>
Seperti yang Anda lihat, Python tidak menganggap b () berbeda dari a (). Dalam Python semua metode hanyalah variabel yang merupakan fungsi.
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:
def addmethod(obj, name, func):
klass = obj.__class__
subclass = type(klass.__name__, (klass,), {})
setattr(subclass, name, func)
obj.__class__ = subclass
from types import MethodType
def method(self):
print 'hi!'
setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )
Dengan ini, Anda dapat menggunakan penunjuk diri
MethodType
, aktifkan protokol deskriptor secara manual dan mintalah fungsi menghasilkan instance Anda:barFighters.__get__(a)
menghasilkan metodebarFighters
terikat untuk terikata
.