Python __call__详解
20210507
相當于函數的默認調用()?
可以調用的對象
關于 __call__ 方法,不得不先提到一個概念,就是可調用對象(callable),我們平時自定義的函數、內置函數和類都屬于可調用對象,但凡是可以把一對括號()應用到某個對象身上都可稱之為可調用對象,判斷對象是否為可調用對象可以用函數 callable
如果在類中實現了 __call__ 方法,那么實例對象也將成為一個可調用對象,
你也許已經知道,在Python中,方法也是一種高等的對象。這意味著他們也可以被傳遞到方法中就像其他對象一樣。這是一個非常驚人的特性。 在Python中,一個特殊的魔術方法可以讓類的實例的行為表現的像函數一樣,你可以調用他們,將一個函數當做一個參數傳到另外一個函數中等等。這是一個非常強大的特性讓Python編程更加舒適甜美。 __call__(self, [args...])
允許一個類的實例像函數一樣被調用。實質上說,這意味著 x() 與 x.__call__() 是相同的。注意 __call__ 參數可變。這意味著你可以定義 __call__ 為其他你想要的函數,無論有多少個參數。
__call__ 在那些類的實例經常改變狀態的時候會非常有效。調用這個實例是一種改變這個對象狀態的直接和優雅的做法。用一個實例來表達最好不過了:
class Entity:
'''調用實體來改變實體的位置。'''def __init__(self, size, x, y):self.x, self.y = x, yself.size = sizedef __call__(self, x, y):'''改變實體的位置'''self.x, self.y = x, ye = Entity(1, 2, 3) // 創建實例
e(4, 5) //實例可以象函數那樣執行,并傳入x y值,修改對象的x y
實例 --- Flask Wtform
在wtform的validators就使用了這個特性
wtform 定義字段的時候可以為每個字段添加校驗器, 而每個校驗器的特點都是接受兩個參數,form, field。
所以我們可以自己自定義校驗器,校驗器是可調用對象即可,即函數,對象方法,都可以的。
比如function url_validate(form, field)
使用函數定制
下面提供一個my_length_check()函數,用于驗證name長達是否長于50個字符。
這個函數按照規定接受兩個參數,form, field,然后就可以根據兩個參數進行判斷。
這樣做是可以的,但是問題是:如果我想自定義錯誤信息怎么辦?而且我想限制的數量也能控制,不是寫死, 還有如果有最小值,和最大值呢? 這里只能固定接受兩個參數,不能再傳入自定參數,沒得更多的自定義了,這樣就會限制住了。
def my_length_check(form, field):if len(field.data) > 50:raise ValidationError('Field must be less than 50 characters')class MyForm(Form):name = StringField('Name', [InputRequired(), my_length_check])
使用閉包和工廠模式
為了解決上面能傳入更多自定義參數, 更靈活定制校驗,我們進行改造。
使用工廠模式,一個能生產校驗器的工廠,而這個工廠能接受其他更靈活的參數,看例子:
def length(min=-1, max=-1, message=None):if not message:message = 'Must be between %d and %d characters long.' % (min, max)def _length(form, field):l = field.data and len(field.data) or 0if l < min or max != -1 and l > max:raise ValidationError(message)return _lengthclass MyForm(Form):name = StringField('Name', [InputRequired(), length(max=50)])
這里涉及兩個概念:
-
工廠模式
這個例子的length()是一個工廠,他的工作就是執行的時候,生產一個validator校驗器,每次調用,傳入不同參數,就會生產不同的校驗器,所以叫做工廠模式。 -
閉包
同時它也是個閉包, 為什么是個閉包?正常情況下執行完length() min, max, message參數就會被回收,不在存在,但是為什么_length()能繼續讀取min, max, message變量,就是閉包的威力。閉包使得局部變量在函數外被訪問成為可能。
這里的 length() 就是一個閉包,閉包本質上是一個函數,它有兩部分組成,_length 函數和變量 min, max, message。閉包使得這些變量的值始終保存在內存中。
參考:一步一步教你認識Python閉包
這個length(min, max, message) 函數,調用的時候,傳入了更多的參數來靈活決定校驗器,因為他里面就是返回一個_length的校驗器,這個校驗器還是遵循規則,只接受form, field 參數, 但是在length() 這個外層卻能接受更多參數,而這些參數也能被內層的_length()所使用。
class MyForm(Form):name = StringField('Name', [InputRequired(), length(max=50)])
這里的length(max=5)就是返回了個_length函數,wtform調用校驗器的時候實際上就是這樣調用的:
length(max=50)(form, field)
使用類方法__call__實現
class Length(object):def __init__(self, min=-1, max=-1, message=None):self.min = minself.max = maxif not message:message = u'Field must be between %i and %i characters long.' % (min, max)self.message = messagedef __call__(self, form, field):l = field.data and len(field.data) or 0if l < self.min or self.max != -1 and l > self.max:raise ValidationError(self.message)class MyForm(Form):name = StringField('Name', [InputRequired(), Length(max=50)])
這次我們使用類來實現,首先實例化這個校驗器Length(max=50), 這時返回的是對象實例,然后我們定義了方法__call__方法,說明實例對象是可以調用的, 最后的結果就是Length(max=50)(form, field)。
這樣實現方法是也是很妙的。
說到這里,類實現方法跟閉包很像,都是把變量封裝起來,讓真正的校驗器能讀取到參數。
閉包避免了使用全局變量,此外,閉包允許將函數與其所操作的某些數據(環境)關連起來。這一點與面向對象編程是非常類似的,在面對象編程中,對象允許我們將某些數據(對象的屬性)與一個或者多個方法相關聯。
__call__其他作用
實例對象也可以像函數一樣作為可調用對象來用,那么,這個特點在什么場景用得上呢?這個要結合類的特性來說,類可以記錄數據(屬性),而函數不行(閉包某種意義上也可行),利用這種特性可以實現基于類的裝飾器,在類里面記錄狀態,比如,下面這個例子用于記錄函數被調用的次數:
class Counter:def __init__(self, func):self.func = funcself.count = 0def __call__(self, *args, **kwargs):self.count += 1return self.func(*args, **kwargs)@Counter
def foo():passfor i in range(10):foo()print(foo.count) # 10 首先這里的@Counter是裝飾器,執行起來順序是 foo = Counter(foo), 實例化,把foo函數傳到類Counter里面,并存到對象屬性里面,然后返回foo = Counter實例。 這時foo已經是Counter實例,而不是本身foo函數。
當執行foo()的時候,其實已經變成了,執行__call__函數,而這個函數里面是執行了本身的self.func 即foo的實際邏輯, 而且加上了計算調用次數。這樣就記錄狀態了。
太厲害了,這樣的實現方式。
https://wtforms.readthedocs.io/en/stable/validators.html#custom-validators
https://stackoverflow.com/questions/9663562/what-is-the-difference-between-init-and-call-in-python
一步一步教你認識Python閉包
簡述 init、new、call 方法
作者:大富帥
鏈接:https://www.jianshu.com/p/e1d95c4e1697
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
總結
以上是生活随笔為你收集整理的Python __call__详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python any()和all()用法
- 下一篇: torch.max