python中的回調和關於首參數的綁定的說法 !!

函數參數的綁定和調用方式

這裏想討論的問題是,如果把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)```
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章