Python 回調和首參數綁定

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

這裏想討論的問題是,若是把python的方法做爲參數傳遞給其餘對象調用,那麼相應的python實例是如何綁定的?html

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,可是第一個參數老是調用方法的類實例。python

類方法的第一個參數必然是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)

而且它們確實能很好地工做。ui

或許大家注意到了,C()建立了一個匿名的C類實例,而後將這個實例的instance_method_1交給了cb,這看起來是不安全的。this

在C++中,相同的方法須要顯式地將實例指針與實例方法綁定成一個函數對象。C++的實例方法確實是一個「函數」,它的this沒有python的self那麼魔幻。指針

直覺上感受不安全,是由於 python 的對象是自動回收的,並且咱們能看得出來:C()彷佛沒有引用了。code

但其實不是,實例方法instance.method實際上已經綁定了instancemethod,在實例方法也失去引用以前,instance不會被釋放。htm

這也是爲何instance_method = instance.method後,instance_method()工做得和instance.method()同樣好的緣由:這是python的魔法,將實例和實例方法綁定在了一塊兒。對象

語法糖?

那麼這種綁定是否是語法糖呢...

我也不知道(誒嘿)。語言規範查閱起來太麻煩了,稍稍不求甚解一下,看看 CPython 這個官方的實現是怎麼處理的吧。

類.實例方法

首先是第一問:實例方法若是用class.method的方式調用,self參數會綁定成類對象嗎?

class C:
	def method(self):
		print(self)

C.method()

輸出是

Traceback (most recent call last):
  File "打碼:/打碼/打碼/打碼/test.py", line 6, in <module>
    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)
相關文章
相關標籤/搜索