您現在的位置是:首頁 > 動作武俠首頁動作武俠

python 魔法函式

  • 由 和馬士兵一起學Python 發表于 動作武俠
  • 2022-04-07
簡介還需要定義__iter____len__(self):返回容器的長度__getitem__(self,key):當需要執行self[key]的方式去呼叫容器中的物件,呼叫的時該方法__setitem__(self,key,value):當需

人類怎樣才能有魔法

Python魔法函式

本篇部落格主要介紹Python的魔法函式。在進行深度學習的工作或者python的程式設計時,或多或少會涉及到Python的類編寫,其中會涉及到python的魔法函式,如編寫一個數據載入的生成器的時候,可能會涉及到

__next__

__iter__

函式,當然生成器可能一個關鍵字

yield

就可以搞定了。最後為了加深對Python魔法函式的理解,這篇部落格以程式碼加說明的方式,記錄一些常見的Python魔法函式。

魔法函式

魔法方法是Python的內建函式,一般以雙下劃線開頭,每個魔法方法對應的一個內建函式或者運算子,比如當使用

len(obj)

的時候實際上是呼叫

obj。__len__

方法。因此當我們物件使用這些方法的時候,相當於對這個物件的這類方法進行重寫或過載。

透過

dir()

可以檢視物件的所有方法和屬性,其中雙下劃綫開頭和結尾的就是該物件具有的魔法方法。以整數物件為例:

python 魔法函式

常用的魔法方法

常用的魔法方法大致可以分為以下幾類:

構造與初始化

類的表示

訪問控制

比較、運算等操作

容器類操作

可呼叫物件

序列化

類構造與初始化

對人類的初始化一般涉及到三個魔法方法:

__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}<__main__。A object at 0x000001BCD98FB3D0>this is A init#再進入__init__<__main__。A object at 0x000001D0BC3EB3D0>#self就是__new__返回的物件例項

__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}>>>print(m。a)#報錯,未進入到當前類的__init__進行初始化AttributeError: ‘NoneType’ object has no attribute ‘a’

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的魔法函式。

Top