您現在的位置是:首頁 > 動作武俠首頁動作武俠
python 魔法函式
- 2022-04-07
人類怎樣才能有魔法
Python魔法函式
本篇部落格主要介紹Python的魔法函式。在進行深度學習的工作或者python的程式設計時,或多或少會涉及到Python的類編寫,其中會涉及到python的魔法函式,如編寫一個數據載入的生成器的時候,可能會涉及到
__next__
,
__iter__
函式,當然生成器可能一個關鍵字
yield
就可以搞定了。最後為了加深對Python魔法函式的理解,這篇部落格以程式碼加說明的方式,記錄一些常見的Python魔法函式。
魔法函式
魔法方法是Python的內建函式,一般以雙下劃線開頭,每個魔法方法對應的一個內建函式或者運算子,比如當使用
len(obj)
的時候實際上是呼叫
obj。__len__
方法。因此當我們物件使用這些方法的時候,相當於對這個物件的這類方法進行重寫或過載。
透過
dir()
可以檢視物件的所有方法和屬性,其中雙下劃綫開頭和結尾的就是該物件具有的魔法方法。以整數物件為例:
常用的魔法方法
常用的魔法方法大致可以分為以下幾類:
構造與初始化
類的表示
訪問控制
比較、運算等操作
容器類操作
可呼叫物件
序列化
類構造與初始化
對人類的初始化一般涉及到三個魔法方法:
__init__
,
__new__
,
__del__
。
初始化一個類的時候,比如
class_a = class_A(1)
,首先呼叫的是該類的
__new__
方法,返回該類的例項物件,然後該類的
__init__
方法,是對該物件進行初始化。
__new__
方法使用如下:
__new__(cls,*args,**kwargs)
:至少要有一個引數cls,代表傳入的類,此引數在例項化時由 Python 直譯器自動提供,若返回該類的物件例項,後面的引數直接傳遞給
__init__
。
class A: def __init__(self,a,b): print(‘this is A init’) print(self) self。a=a self。b=b def __new__(cls, *args, **kwargs): print(‘this is A new’) print(‘args:%s’%args) print(‘kwargs:%s’%kwargs) print(cls) print(object。__new__(cls))#<__main__。A object at 0x000001BCD98FB3D0>,一個A的物件例項 return object。__new__(cls)#建立例項並返回>>>a=A(1,b=10)this is A new#先進入__new__args:1kwargs:{‘b’: 10}
__new__
可以決定是否使用
__init__
方法,但是,執行了
__new__
,並不一定會進入
__init__
,只有
__new__
返回了,當前類cls的例項,當前類的
__init__
才會進入。即使返回父類的例項也不行,必須是當前類的例項;
class A: def __init__(self,a,b): print(‘this is A init’) self。a=a self。b=b def __new__(cls, *args, **kwargs): print(‘this is A new’) print(‘args:%s’%args) print(‘kwargs:%s’%kwargs) print(cls)>>>m=A(1,b=10)this is A newargs:1kwargs:{‘b’: 10}
object將
__new__()
方法定義為靜態方法,並且至少需要傳遞一個引數cls,cls表示需要例項化的類,此引數在例項化時由Python直譯器自動提供。
__init__()
有一個引數self,該self引數就是
__new__()
返回的例項
__new__
的使用場景如單例模式、工廠模式,以及一些不可變物件的繼承上。這類應用非常值得關注並使用,可以大大地讓程式碼看起來優美和簡潔;
__del__
方法則是當物件被系統回收的時候呼叫的魔法方法,在物件生命週期呼叫結束時呼叫該方法。Python 採用自動引用計數(ARC)方式來回收物件所佔用的空間,當程式中有一個變數引用該 Python 物件時,Python 會自動保證該物件引用計數為 1;當程式中有兩個變數引用該 Python 物件時,Python 會自動保證該物件引用計數為 2,依此類推,如果一個物件的引用計數變成了 0,則說明程式中不再有變數引用該物件,表明程式不再需要該物件,因此 Python 就會回收該物件。所以大部分時候,都不需要我們手動去刪掉不再使用的物件,python的回收機制會自動幫我們做這件事。
類的表示
類的表示相關的魔法方法主要有
__str__
、
__repr__
和
__bool__
__str__
主要是在列印物件print(obj)時,會隱式呼叫str(obj),即呼叫類中的
__str__
方法;定了該方法就可以透過str(obj)來呼叫;
__repr__
主要是在直接輸出物件時的顯示,會呼叫
__repr__
方法;定義了該方法就可以透過repr(obj)來呼叫。
__bool__
:當呼叫 bool(obj) 時,會呼叫
__bool__()
方法,返回 True 或 False:
當自定義類中沒有定義
__str__()
和
__repr__()
時,在進行物件的輸出時,會呼叫預設的
__str__()
和
__repr__()
;當類中只包含
__str__()
時,呼叫
print()
或
str()
函式進行物件的輸出,會呼叫
__str__()
,直接輸出呼叫預設的
__repr__()
;當類中既包含
__str__()
又包含
__repr__()
時,呼叫
print()
或
str()
函式進行物件的輸出,會呼叫
__str__()
,直接輸出會呼叫
__repr__()
;當類中只包含
__repr__()
時,呼叫 print() str()函式進行物件的輸出和直接輸出都會呼叫
__repr__()
。
因此,對於自定義的類,建議定義__str__和__repr__,以更好地進行互動;其中__str__可以考慮設計為想轉換輸出的字串,在後續str(obj)將物件轉為特定的字串輸出時提供一些便利;__repr__可以考慮設計為輸出較為詳細的資訊,如列名,甚至包括部分關鍵引數名,這樣在開發的時候便於獲取物件的準確資訊(如sklearn中的機器學習模組就是如此設計)
控制屬性訪問
這類魔法方法主要在對物件的屬性進行訪問、定義、修改時起作用。主要有:
__getattr__(self, name)
: 定義為當用戶試圖獲取一個屬性時的行為。
__getattribute__(self, name)
:定義當該類的屬性被訪問時的行為(先呼叫該方法,檢視是否存在該屬性,若不存在,接著去呼叫
__getattr__
)。
__setattr__(self, name, value)
:定義當一個屬性被設定時的行為。
__delattr__(self, name)
:定義當一個屬性被刪除時的行為。
class A(object): def __init__(self,a,b): self。a=a self。b=b def __setattr__(self, key, value): print(key,value) print(‘this is magic method setattr’) def __getattr__(self, item): print(‘getattr:%s’%item) print(‘this is magic method getattr’) def __delattr__(self, item): print(‘delattr:%s’%item) print(‘this is magic method delattr’) >>>m=A(1,2)a 1this is magic method setattr#初始化self。a=a時呼叫 __setattr__b 2this is magic method setattr#初始化self。b=b時呼叫 __setattr__>>>a=m。agetattr:athis is magic method getattr#訪問屬性a時呼叫__getattr__>>>m。b=100b 100this is magic method setattr#修改屬性b時呼叫__setattr__>>>delattr(m,‘a’)delattr:athis is magic method delattr#刪除屬性a時呼叫__delattr__>>>print(m。a)getattr:athis is magic method getattrNone#屬性a被刪除,為None
在上面程式碼中,過載了
__setattr__
,因此屬性初始化時就呼叫過載後的
__setattr__
;但是在初始化屬性呼叫
__setattr__
時需要配合例項的屬性管理
__dict__
來進行,即需要將屬性都在
self。__dict__
中進行註冊,否則例項是訪問不到這些屬性的。
>>>print(m。a)getattr:athis is magic method getattrNone#可以看到,並沒有初始化成功為a=1,因為過載的__setattr__方法內部尚未將屬性在__dict__中註冊#修改上面的__setattr__:class A(object): def __init__(self,a,b): self。a=a self。b=b def __setattr__(self, key, value): print(key,value) print(‘this is magic method setattr’) self。__dict__[key] = value#在__dict__註冊例項屬性 #super()。__setattr__(key,value) 也可以透過繼承的方式來實現; def __getattr__(self, item): print(‘getattr:%s’%item) print(‘this is magic method getattr’) def f(self): return self。__dict__#檢視屬性管理字典>>>m=A(1,2)>>>m。a1>>>m。f(){‘a’: 1, ‘b’: 2}
控制屬性過載的使用場景:如在初始化屬性時先對屬性的值進行攔截,進行相應的處理或者判斷(比如型別判斷,或者範圍判斷)
class A(object): def __init__(self,age,sex): self。age=age self。sex=sex def __setattr__(self, key, value): if key==‘age’: if not 0<=value<=100: raise Exception(‘age must between 0 and 100’) elif key==‘age’: if not (value==‘male’ or value==‘female’): raise Exception(‘sex must be male of female’) else: pass super()。__setattr__(key,value)>>>m=A(age=102,sex=‘male’)Exception: age must between 0 and 100>>>m=A(age=90,sex=‘hhh’)Exception: sex must be male of female>>>m=A(age=90,sex=‘male’)>>>print(m。sex,m。age)male 90
比較、運算等操作
透過定義各類比較、運算、型別相關的魔法方法,來實現物件之間的各類比較、運算等操作。這類魔法方法非常多,不一一展開。
用於比較的魔法函式:
雙目運算子或函式
增量運算
型別轉換
容器類操作
有一些方法可以自定義容器,就像python內建的list,tuple,dict等等;容器分為可變容器和不可變容器,這裡的細節需要去了解相關的協議。如果自定義一個不可變容器的話,只能定義
__len__
和
__getitem__
;定義一個可變容器除了不可變容器的所有魔法方法,還需要定義
__setitem__
和
__delitem__
;如果容器可迭代。還需要定義
__iter__
__len__(self)
:返回容器的長度
__getitem__(self,key)
:當需要執行self[key]的方式去呼叫容器中的物件,呼叫的時該方法
__setitem__(self,key,value)
:當需要執行self[key] = value時,呼叫的是該方法。
__delitem__(self, key)
:當需要執行 del self[key]時,需要呼叫該方法;
__iter__(self)
:當容器可以執行 for x in container: ,或者使用iter(container)時,需要定義該方法
__reversed__(self)
:實現當reversed()被呼叫時的行為。應該返回序列反轉後的版本。僅當序列可以是有序的時候實現它,例如對於列表或者元組。
__contains__(self, item)
:定義了呼叫in和not in來測試成員是否存在的時候所產生的行為。
class SpecialList(object): def __init__(self,values=None): if values is None: self。values=[] else: self。values=values self。count={ }。fromkeys(range(len(self。values)),0) def __len__(self):#透過len(obj)訪問容器長度 return len(self。values) def __getitem__(self, key):#透過obj[key]訪問容器內的物件 self。count[key]+=1 return self。values[key] def __setitem__(self, key, value):#透過obj[key]=value去修改容器內的物件 self。values[key]=value def __delitem__(self, key):#透過del obj[key]來刪除容器內的物件 del self。values[key] def __iter__(self):#透過for 迴圈來遍歷容器 return iter(self。values) def __next__(self): # 迭代的具體細節 # 如果__iter__返回時self 則必須實現此方法 if self。_index >= len(self。values): raise StopIteration() value = self。values[self。_index] self。_index += 1 return value def __reversed__(self):#透過reverse(obj)來反轉容器內的物件 return SpecialList(reversed(self。values)) def __contains__(self, item):#透過 item in obj來判斷元素是否在容器內 return item in self。values def append(self, value): self。values。append(value) def head(self): # 獲取第一個元素 return self。values[0] def tail(self): # 獲取第一個元素之後的所有元素 return self。values[1:]
可呼叫物件
在Python中,方法也是一種高等的物件。透過對物件實現
__call__
就可以實現像呼叫方法一樣去呼叫類。
class A(object): def __init__(self,a,b): self。a=a self。b=b def __call__(self,a): self。a=a >>>m=A(1,2)>>>m。a1>>>m。b2>>>id(m)1460475565152>>>m(100)#像函式一樣直接呼叫類,本質是呼叫的__call__方法>>>m。a100>>>m。b2>>>id(m)1460475565152
應用場景:
自建一個簡單的裝飾器(實際例子如 bottle 框架原始碼的 cached_property)
可以用作抽象視窗函式,抽象出使用規則,透過子類的改寫其他方法,在不改變原始碼的情況下取得不同的結果
在序列化的時候也是呼叫的內建魔法方法:
__getstate__()__setstate__()
class A(object): def __init__(self,a,b): self。a=a self。b=b def __getstate__(self): print(‘this is magic method __getstate__’) return { ‘a’:self。a, ‘b’:self。b}#序列化時返回的,即為在反序列化時傳入的state def __setstate__(self, state): print(‘this is magic method __setstate__’) self。a=state[‘a’] self。b=300import pickle>>>a=A(1,2)>>>a_1=pickle。dumps(a)#呼叫__getstate__>>>print(a_1)this is magic method __getstate__b‘\x80\x04\x95&\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01A\x94\x93\x94)\x81\x94}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94K\x02ub。’>>>a_2=pickle。loads(a_1)#呼叫__setstate__>>>print(a_2)this is magic method __setstate__<__main__。A object at 0x000001BF5B086670>>>>print(a_2。a,a_2。b)1 300
希望在後續工程化工作中能夠有意識有意義去運用python的魔法函式。