函數參數的綁定和調用方式
這裏想討論的問題是,如果把python的方法作爲參數傳遞給其他對象調用,那麼相應的python實例是如何綁定的?
class C:
def callback(self):
print('callback')
@staticmethod
def sc():
print('sc')
@classmethod
def cc(cls):
print('cc')
def f():
print('f')
def f_with_1_parameter(param):
print(param)
def do_something(cb):
print('do something')
cb()
if __name__ == '__main__':
instance = C()
# 它們綁定的是誰?
do_something(instance.callback)
do_something(C.sc)
do_something(C.cc)
do_something(f)
do_something(instance.f_with_1_parameter)
do_something(C.f_with_1_parameter)
首參數綁定
python 有這樣的約定,實例方法的第一個參數必然是self,名字可以不叫self,但是第一個參數總是調用方法的類實例。
類方法的第一個參數必然是cls,名字可以不叫cls,但是第一個參數總是調用該方法的類對象。
依照直覺,可以寫出這樣的代碼 :
class C:
@classmethod
def class_method_1(cls):
print('class method 1')
def instance_method_1(self):
print('instance method 1')
def do_something(cb):
cb()
cb(C.class_method_1)
cb(C().instance_method_1)```
並且它們確實能很好地工作。
或許你們注意到了,C()創建了一個匿名的C類實例,然後將這個實例的instance_method_1交給了cb,這看起來是不安全的。
在C++中,相同的方法需要顯式地將實例指針與實例方法綁定成一個函數對象。C++的實例方法確實是一個“函數”,它的this沒有python的self那麼魔幻。
直覺上感覺不安全,是因爲 python 的對象是自動回收的,而且我們能看得出來:C()似乎沒有引用了。
但其實不是,實例方法instance.method實際上已經綁定了instance和method,在實例方法也失去引用之前,instance不會被釋放。
這也是爲什麼instance_method = instance.method後,instance_method()工作得和instance.method()一樣好的原因:這是python的魔法,將實例和實例方法綁定在了一起。
![](https://yqfile.alicdn.com/fc1bfb301cd26b5df1dfdf2024f6e4e900856cb7.png)
**語法糖?**
那麼這種綁定是不是語法糖呢...
我也不知道(誒嘿)。語言規範查閱起來太麻煩了,稍稍不求甚解一下,看看 CPython 這個官方的實現是怎麼處理的吧。
**類.實例方法**
首先是第一問:實例方法如果用class.method的方式調用,self參數會綁定成類對象嗎?
class C:
def method(self):
print(self)
C.method()
輸出是
Traceback (most recent call last):
File "打碼:/打碼/打碼/打碼/test.py", line 6, in
C.method()
TypeError: method() missing 1 required positional argument: 'self
看來並不會,class.method的方式並不會將class綁定爲self傳遞給method,只有通過實例.方法的情況會綁定實例對象到self參數。
**實例.類方法**
class C:
@classmethod
def method(cls):
print (cls)
instance = C()
instance.method()`
這次的結果比較神奇,因爲它正常執行了。
<class '__main__.C'>```
並沒有報錯。
這應該是類似於原型鏈的機制在其中作祟:instance.class_field是可以正常訪問的,不像是C++訪問類變量時需要class::class_field這樣的特殊語法。
classmethod 裝飾器做的事情感覺像是給一個函數包了一層重載了__get__的類,然後這個包了__get__的descriptor打入另一個owner類裏,可以參考下這篇文章Python3 Data Model。
做個具體的實例
def f(cls):
print(cls)
def C:
pass
C.f = classmethod(f)
C.f() # 正常執行 `
這應該是 classmethod裝飾器實現的方式了(猜測)。
無論你是大牛還是小白,是想轉行還是想入行都可以來了解一起進步一起學習!裙內有開發工具,很多幹貨和技術資料分享!
運行時改變類?
如果在運行時給類插入一個實例方法呢?如果插入的實例方法正常運作,說明這僅僅是一個語法糖:實例.方法會綁定self參數,僅此而已。
def f(self):
print(self)
class C:
pass
C.f = f
instance = C()
instance.f()```
輸出是
<__main__.C object at 0x038262B0>
看起來沒錯了,這僅僅是一個語法糖。
**總結**
用實例.方法的形式訪問到的其實是一個這樣子的實例
class WhoCare:
def __init__(self, instance, method):
"""
在調用 實例.方法時,實例.方法構成了這樣的一個奇特對象
當然了,別當真。只是爲了說明這種語法背後做了什麼的理解。
"""
self._i=instance
self._method=method
def __call__(self,*args,**kwargs):
"""
反正固定了第一個 instance 參數,其他參數照樣送進去就 ok
"""
self._method(self._i, *args, **kwargs)```