python __call__(可呼叫物件理解)

2020-08-09 14:52:16

@[TOC](python call(可呼叫物件理解))

前言

在Python中,方法也是一種高等的物件(python:萬物皆物件)。我們平時自定義的函數、內建函數和類都屬於可呼叫物件,但凡是可以把一對括號()應用到某個物件身上都可稱之爲可呼叫物件,判斷物件是否爲可呼叫物件可以用函數 callable。如果在類中實現了 __call__方法,那麼範例物件也將成爲一個可呼叫物件。我們可以自定義可呼叫物件,比如pytorch中的nn.Module類就是利用了__call__實現了範例的可呼叫。

model=nn.Module()
model(x)

實際上在Module裏實現了__call__,並呼叫了forward函數,進行前向傳播。

具體說明

允許一個類的範例像函數一樣被呼叫。實質上說,這意味着 x()x.__call__()是相同的。注意 __call__ 參數可變。這意味着你可以定義__call__爲其他你想要的函數,無論有多少個參數。__call__在那些類的範例經常改變狀態的時候會非常有效。呼叫這個範例是一種改變這個物件狀態的直接和優雅的做法。用一個範例來表達最好不過了:

class Entity(object):
    '''呼叫實體來改變實體的位置。'''

    def __init__(self, size, x, y):
        self.x, self.y = x, y
        self.size = size

    def __call__(self, x, y):
        '''改變實體的位置'''
        self.x, self.y = x, y
        
e = Entity(1, 2, 3)
e(4, 5)

實際上,每一個可呼叫物件都有一個__call__方法。比如我隨便定義一個函數,然後通過兩種方式來呼叫它:

def print_msg(msg):
    print(msg)


print_msg.__call__('hello')
print('hello')

結果是一樣的。

同時,用 call() 彌補 hasattr() 函數的短板。 hasattr() 函數的功能是查詢類的範例物件中是否包含指定名稱的屬性或者方法,但該函數有一個缺陷,即它無法判斷該指定的名稱,到底是類屬性還是類方法。
要解決這個問題,我們可以藉助可呼叫物件的概念。要知道,類範例物件包含的方法,其實也屬於可呼叫物件,但類屬性卻不是。舉個例子

class CLanguage:
    def __init__ (self):
        self.name = "C語言中文網"
        self.add = "http://c.biancheng.net"
    def say(self):
        print("我正在學Python")
clangs = CLanguage()
if hasattr(clangs,"name"):
    print(hasattr(clangs.name,"__call__"))
print("**********")
if hasattr(clangs,"say"):
    print(hasattr(clangs.say,"__call__"))
程式執行結果爲:
False
**********
True

可以看到,由於 name 是類屬性,它沒有以 call 爲名的 call() 方法;而 say 是類方法,它是可呼叫物件,因此它有 call() 方法。

另外,我們可以通過__call__中呼叫我們想要呼叫的方法。參數傳遞可以利用*args**kwards

class Entity(object):
    '''呼叫實體來改變實體的位置。'''

    def __init__(self, x,y):
        self.x,self.y=x,y

    def __call__(self, *args,**kwargs):
        '''改變實體的位置'''
        self.change_loc(*args,**kwargs)

    def change_loc(self,*args,**kwargs):
        self.x,self.y=args[0],args[1]
        print(self.x,self.y)


e = Entity(1, 2)
e(4, 5)