Câu trả lời:
Trong Python, có một sự khác biệt giữa các hàm và các phương thức ràng buộc.
>>> 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>>
>>>
Các phương thức ràng buộc đã được "ràng buộc" (mô tả như thế nào) với một thể hiện và thể hiện đó sẽ được truyền làm đối số đầu tiên bất cứ khi nào phương thức được gọi.
Mặc dù vậy, các hàm gọi là thuộc tính của một lớp (trái ngược với một thể hiện) vẫn không bị ràng buộc, vì vậy bạn có thể sửa đổi định nghĩa lớp bất cứ khi nào bạn muốn:
>>> 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
Các trường hợp được xác định trước đó cũng được cập nhật (miễn là chúng không ghi đè lên thuộc tính):
>>> a.fooFighters()
fooFighters
Vấn đề xảy ra khi bạn muốn đính kèm một phương thức vào một thể hiện duy nhất:
>>> 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)
Hàm không tự động bị ràng buộc khi được gắn trực tiếp vào một thể hiện:
>>> a.barFighters
<function barFighters at 0x00A98EF0>
Để liên kết nó, chúng ta có thể sử dụng hàm MethodType trong mô đun kiểu :
>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters
Lần này các trường hợp khác của lớp không bị ảnh hưởng:
>>> a2.barFighters()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'
Thông tin thêm có thể được tìm thấy bằng cách đọc về mô tả và lập trình siêu dữ liệu .
descriptor protocol
vs tạo ra một MethodType
bên có thể dễ đọc hơn một chút.
classmethod
và staticmethod
và mô tả khác nữa. Nó tránh làm lộn xộn không gian tên với một lần nhập khác.
a.barFighters = barFighters.__get__(a)
Mô-đun mới không được dùng nữa từ python 2.6 và bị xóa trong 3.0, sử dụng các loại
xem http://docs.python.org/l Library / new.html
Trong ví dụ dưới đây, tôi đã cố tình xóa giá trị trả về khỏi patch_me()
hàm. Tôi nghĩ rằng việc đưa ra giá trị trả về có thể khiến người ta tin rằng bản vá trả về một đối tượng mới, điều đó không đúng - nó sửa đổi đối tượng đến. Có lẽ điều này có thể tạo điều kiện cho việc sử dụng con khỉ có kỷ luật hơn.
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'>
Lời nói đầu - một lưu ý về khả năng tương thích: các câu trả lời khác chỉ có thể hoạt động trong Python 2 - câu trả lời này sẽ hoạt động hoàn hảo trong Python 2 và 3. Nếu chỉ viết Python 3, bạn có thể bỏ qua kế thừa rõ ràng object
, nhưng nếu không thì mã vẫn giữ nguyên .
Thêm một phương thức vào một đối tượng hiện có
Tôi đã đọc rằng có thể thêm một phương thức vào một đối tượng hiện có (ví dụ không phải trong định nghĩa lớp) trong Python.
Tôi hiểu rằng không phải lúc nào cũng là một quyết định tốt để làm như vậy. Nhưng, làm thế nào người ta có thể làm điều này?
Tôi không khuyên bạn điều này. Đây là một ý tưởng tồi. Đừng làm điều đó.
Đây là một vài lý do:
Vì vậy, tôi khuyên bạn không nên làm điều này trừ khi bạn có lý do thực sự tốt. Sẽ tốt hơn nhiều khi định nghĩa phương thức đúng trong định nghĩa lớp hoặc ít nhất là tốt hơn để vá trực tiếp lớp, như sau:
Foo.sample_method = sample_method
Vì đó là hướng dẫn, tuy nhiên, tôi sẽ chỉ cho bạn một số cách để làm điều này.
Đây là một số mã thiết lập. Chúng ta cần một định nghĩa lớp. Nó có thể được nhập khẩu, nhưng nó thực sự không quan trọng.
class Foo(object):
'''An empty class to demonstrate adding a method to an instance'''
Tạo một ví dụ:
foo = Foo()
Tạo một phương thức để thêm vào nó:
def sample_method(self, bar, baz):
print(bar + baz)
__get__
Tra cứu chấm trên các hàm gọi __get__
phương thức của hàm với thể hiện, liên kết đối tượng với phương thức và do đó tạo ra "phương thức ràng buộc".
foo.sample_method = sample_method.__get__(foo)
và bây giờ:
>>> foo.sample_method(1,2)
3
Đầu tiên, nhập các loại, từ đó chúng ta sẽ có hàm tạo phương thức:
import types
Bây giờ chúng ta thêm phương thức vào ví dụ. Để làm điều này, chúng tôi yêu cầu hàm tạo Phương thức Kiểu từ types
mô-đun (mà chúng tôi đã nhập ở trên).
Chữ ký đối số cho các loại.MethodType là (function, instance, class)
:
foo.sample_method = types.MethodType(sample_method, foo, Foo)
và cách sử dụng:
>>> foo.sample_method(1,2)
3
Đầu tiên, chúng ta tạo một hàm bao bọc liên kết phương thức với thể hiện:
def bind(instance, method):
def binding_scope_fn(*args, **kwargs):
return method(instance, *args, **kwargs)
return binding_scope_fn
sử dụng:
>>> foo.sample_method = bind(foo, sample_method)
>>> foo.sample_method(1,2)
3
Hàm một phần áp dụng (các) đối số đầu tiên cho một hàm (và các đối số từ khóa tùy chọn) và sau đó có thể được gọi với các đối số còn lại (và ghi đè các đối số từ khóa). Như vậy:
>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3
Điều này có ý nghĩa khi bạn xem xét rằng các phương thức ràng buộc là các hàm một phần của thể hiện.
Nếu chúng ta cố gắng thêm sample_method theo cùng một cách như chúng ta có thể thêm nó vào lớp, thì nó không bị ràng buộc từ thể hiện và không lấy bản thân ẩn làm đối số đầu tiên.
>>> 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)
Chúng ta có thể làm cho hàm không liên kết hoạt động bằng cách chuyển rõ ràng thể hiện (hoặc bất cứ điều gì, vì phương thức này không thực sự sử dụng self
biến đối số), nhưng nó sẽ không phù hợp với chữ ký dự kiến của các trường hợp khác (nếu chúng ta đang vá khỉ ví dụ này):
>>> foo.sample_method(foo, 1, 2)
3
Bây giờ bạn biết một số cách bạn có thể làm điều này, nhưng thực sự nghiêm túc - đừng làm điều này.
__get__
phương pháp cũng cần lớp như tham số tiếp theo: sample_method.__get__(foo, Foo)
.
Tôi nghĩ rằng các câu trả lời trên đã bỏ lỡ điểm quan trọng.
Chúng ta hãy có một lớp học với một phương thức:
class A(object):
def m(self):
pass
Bây giờ, hãy chơi với nó trong ipython:
In [2]: A.m
Out[2]: <unbound method A.m>
Ok, vậy m () bằng cách nào đó sẽ trở thành một phương pháp không ràng buộc của Một . Nhưng nó có thực sự như vậy không?
In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>
Hóa ra m () chỉ là một hàm, tham chiếu được thêm vào từ điển lớp A - không có phép thuật. Vậy thì tại sao Am lại cho chúng ta một phương pháp không ràng buộc? Đó là vì dấu chấm không được dịch sang tra cứu từ điển đơn giản. Đây thực tế là một cuộc gọi của lớp A .__ __.__ getattribution __ (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
Bây giờ, tôi không chắc chắn ra khỏi đầu của mình tại sao dòng cuối cùng được in hai lần, nhưng vẫn rõ ràng những gì đang xảy ra ở đó.
Bây giờ, những gì __getattribution__ mặc định làm là nó kiểm tra xem thuộc tính có được gọi là mô tả hay không, tức là nếu nó thực hiện một phương thức __get__ đặc biệt. Nếu nó thực hiện phương thức đó, thì cái được trả về là kết quả của việc gọi phương thức __get__ đó. Quay trở lại phiên bản đầu tiên của lớp A của chúng tôi , đây là những gì chúng tôi có:
In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>
Và bởi vì các hàm Python thực hiện giao thức mô tả, nếu chúng được gọi thay mặt cho một đối tượng, chúng tự liên kết với đối tượng đó trong phương thức __get__ của chúng.
Ok, vậy làm thế nào để thêm một phương thức vào một đối tượng hiện có? Giả sử bạn không bận tâm đến việc vá lớp, nó đơn giản như:
B.m = m
Sau đó, Bm "trở thành" một phương thức không liên kết, nhờ vào phép thuật mô tả.
Và nếu bạn muốn thêm một phương thức chỉ vào một đối tượng, thì bạn phải tự mô phỏng máy móc bằng cách sử dụng các loại.MethodType:
b.m = types.MethodType(m, b)
Nhân tiện:
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'>
Trong Python patch patch thường hoạt động bằng cách ghi đè một lớp hoặc chữ ký hàm với chữ ký của riêng bạn. Dưới đây là một ví dụ từ Zope Wiki :
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
Mã đó sẽ ghi đè / tạo một phương thức gọi là speak trên lớp. Trong bài đăng gần đây của Jeff Atwood về việc vá khỉ . Anh ta cho thấy một ví dụ trong C # 3.0, ngôn ngữ hiện tại tôi sử dụng cho công việc.
Bạn có thể sử dụng lambda để liên kết một phương thức với một thể hiện:
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()
Đầu ra:
This is instance string
Có ít nhất hai cách để đính kèm một phương thức vào một cá thể mà không có 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>>
Liên kết hữu ích:
Mô hình dữ liệu - gọi mô tả
Mô tả Hướng dẫn cách làm - gọi mô tả
Những gì bạn đang tìm kiếm là setattr
tôi tin tưởng. Sử dụng điều này để đặt một thuộc tính trên một đối tượng.
>>> 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
, không phải là ví dụ a
.
setattr(A,'printme',printme)
thay vì đơn giản A.printme = printme
?
Vì câu hỏi này yêu cầu các phiên bản không phải Python, nên đây là JavaScript:
a.methodname = function () { console.log("Yay, a new method!") }
Hợp nhất các câu trả lời wiki của Jason Pratt và cộng đồng, với cái nhìn về kết quả của các phương pháp ràng buộc khác nhau:
Đặc biệt lưu ý cách thêm chức năng liên kết như một phương thức lớp hoạt động , nhưng phạm vi tham chiếu không chính xác.
#!/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>>
Cá nhân, tôi thích tuyến chức năng ADDMETHOD bên ngoài, vì nó cho phép tôi tự động gán tên phương thức mới trong một trình vòng lặp.
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
viết lại theo cách sau đây def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )
giải quyết vấn đề
Mặc dù câu trả lời của Jasons hoạt động, nó chỉ hoạt động nếu một người muốn thêm một hàm vào một lớp. Nó không hoạt động với tôi khi tôi cố tải lại một phương thức đã có từ tệp mã nguồn .py.
Tôi mất nhiều thời gian để tìm ra cách giải quyết, nhưng thủ thuật có vẻ đơn giản ... 1. nhập mã từ tệp mã nguồn 2. và buộc tải lại 3.rd sử dụng các loại.FeftType (...) để chuyển đổi phương thức được nhập và ràng buộc vào một hàm mà bạn cũng có thể truyền vào các biến toàn cục hiện tại, vì phương thức được tải lại sẽ ở một không gian tên khác 4. bây giờ bạn có thể tiếp tục như được đề xuất bởi "Jason Pratt" bằng cách sử dụng các loại.MethodType (... )
Thí dụ:
# 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
Nếu nó có thể giúp được gì, gần đây tôi đã phát hành một thư viện Python có tên Gorilla để làm cho quá trình vá khỉ thuận tiện hơn.
Sử dụng một chức năng needle()
để vá một mô-đun có tên guineapig
như sau:
import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
print("awesome")
Nhưng nó cũng quan tâm đến các trường hợp sử dụng thú vị hơn như được nêu trong Câu hỏi thường gặp từ tài liệu .
Mã này có sẵn trên GitHub .
Câu hỏi này đã được mở từ nhiều năm trước, nhưng này, có một cách dễ dàng để mô phỏng sự ràng buộc của một chức năng với một thể hiện của lớp bằng cách sử dụng các trình trang trí:
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 ()
Ở đó, khi bạn truyền hàm và thể hiện cho trình trang trí chất kết dính, nó sẽ tạo ra một hàm mới, với cùng một đối tượng mã như hàm đầu tiên. Sau đó, thể hiện đã cho của lớp được lưu trữ trong một thuộc tính của hàm vừa tạo. Trình trang trí trả về một hàm (thứ ba) tự động gọi hàm được sao chép, đưa ra thể hiện làm tham số đầu tiên.
Để kết luận, bạn nhận được một hàm mô phỏng nó liên kết với thể hiện của lớp. Để chức năng ban đầu không thay đổi.
Những gì Jason Pratt đăng là chính xác.
>>> 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'>
Như bạn có thể thấy, Python không xem xét b () bất kỳ khác với a (). Trong Python tất cả các phương thức chỉ là các biến xảy ra là các hàm.
Test
, không phải là một thể hiện của nó.
Tôi thấy lạ là không ai đề cập rằng tất cả các phương thức được liệt kê ở trên tạo ra một tham chiếu chu kỳ giữa phương thức được thêm vào và thể hiện, làm cho đối tượng bị tồn tại cho đến khi thu gom rác. Có một thủ thuật cũ khi thêm một mô tả bằng cách mở rộng lớp của đối tượng:
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)) )
Với điều này, bạn có thể sử dụng con trỏ tự
MethodType
, hãy gọi giao thức mô tả theo cách thủ công và có chức năng tạo ra cá thể của bạn:barFighters.__get__(a)
tạo ra một phương thứcbarFighters
ràng buộc cho ràng buộca
.