在Python中以兩個下劃線開頭和結尾的方法,好比:__init__、__str__、__doc__、__new__等,被稱爲"魔術方法"(Magic methods)。魔術方法在類或對象的某些事件出發後會自動執行,若是但願根據本身的程序定製本身特殊功能的類,那麼就須要對這些方法進行重寫。算法
Python 將全部以 __(兩個下劃線)開頭和結尾的類方法保留爲魔術方法。因此在定義類方法時,除了上述魔術方法,建議不要以 __ 爲前綴。編程
咱們將不一樣類型的魔術方法進行歸類,那麼會分爲如下幾類。app
魔術方法 | 含義 |
---|---|
__new__(cls[, ...]) | 1. __new__ 是在一個對象實例化的時候所調用的第一個方法 2. 它的第一個參數是這個類,其餘的參數是用來直接傳遞給 __init__ 方法 3. __new__ 決定是否要使用該 __init__ 方法,由於 __new__ 能夠調用其餘類的構造方法或者直接返回別的實例對象來做爲本類的實例,若是 __new__ 沒有返回實例對象,則 __init__ 不會被調用 4. __new__ 主要是用於繼承一個不可變的類型好比一個 tuple 或者 string |
__init__(self[, ...]) | 構造器,當一個實例被建立的時候調用的初始化方法 |
__del__(self) | 析構器,當一個實例被銷燬的時候調用的方法 |
__call__(self[, args...]) | 容許一個類的實例像函數同樣被調用:x(a, b) 調用 x.__call__(a, b) |
__len__(self) | 定義當被 len() 調用時的行爲 |
__repr__(self) | 定義當被 repr() 調用或者直接執行對象時的行爲 |
__str__(self) | 定義當被 str() 調用或者打印對象時的行爲 |
__bytes__(self) | 定義當被 bytes() 調用時的行爲 |
__hash__(self) | 定義當被 hash() 調用時的行爲 |
__bool__(self) | 定義當被 bool() 調用時的行爲,應該返回 True 或 False |
__format__(self, format_spec) | 定義當被 format() 調用時的行爲 |
__name__ | 類、函數、方法等的名字 |
__module__ | 類定義所在的模塊名 |
__class__ | 對象或類所屬的類 |
__bases__ | 類的基類元組,順序爲它們在基類列表中出現的順序 |
__doc__ | 類、函數的文檔字符串,若是沒有定義則爲None |
__mro__ | 類的mro,class.mro()返回的結果保存在__mro__中 |
__dict__ | 類或實例的屬性,可寫的字典 |
魔術方法 | 含義 |
---|---|
__getattr__(self, name) | 定義當用戶試圖獲取一個不存在的屬性時的行爲 |
__getattribute__(self, name) | 定義當該類的屬性被訪問時的行爲 |
__setattr__(self, name, value) | 定義當一個屬性被設置時的行爲 |
__delattr__(self, name) | 定義當一個屬性被刪除時的行爲 |
__dir__(self) | 定義當 dir() 被調用時的行爲 |
__get__(self, instance, owner) | 定義當描述符的值被取得時的行爲 |
__set__(self, instance, value) | 定義當描述符的值被改變時的行爲 |
__delete__(self, instance) | 定義當描述符的值被刪除時的行爲 |
魔術方法 | 含義 |
---|---|
__lt__(self, other) | 定義小於號的行爲:x < y 調用 x.__lt__(y) |
__le__(self, other) | 定義小於等於號的行爲:x <= y 調用 x.__le__(y) |
__eq__(self, other) | 定義等於號的行爲:x == y 調用 x.__eq__(y) |
__ne__(self, other) | 定義不等號的行爲:x != y 調用 x.__ne__(y) |
__gt__(self, other) | 定義大於號的行爲:x > y 調用 x.__gt__(y) |
__ge__(self, other) | 定義大於等於號的行爲:x >= y 調用 x.__ge__(y) |
魔術方法 | 含義 |
---|---|
__add__(self, other) | 定義加法的行爲:+ |
__sub__(self, other) | 定義減法的行爲:- |
__mul__(self, other) | 定義乘法的行爲:* |
__truediv__(self, other) | 定義真除法的行爲:/ |
__floordiv__(self, other) | 定義整數除法的行爲:// |
__mod__(self, other) | 定義取模算法的行爲:% |
__divmod__(self, other) | 定義當被 divmod() 調用時的行爲 |
__pow__(self, other[, modulo]) | 定義當被 power() 調用或 ** 運算時的行爲 |
__lshift__(self, other) | 定義按位左移位的行爲:<< |
__rshift__(self, other) | 定義按位右移位的行爲:>> |
__and__(self, other) | 定義按位與操做的行爲:& |
__xor__(self, other) | 定義按位異或操做的行爲:^ |
__or__(self, other) | 定義按位或操做的行爲: |
魔術方法 | 含義 |
---|---|
__radd__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rsub__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rmul__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rtruediv__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rfloordiv__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rmod__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rdivmod__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rpow__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rlshift__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rrshift__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rand__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__rxor__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
__ror__(self, other) | (與上方相同,當左操做數不支持相應的操做時被調用) |
魔術方法 | 含義 |
---|---|
__iadd__(self, other) | 定義賦值加法的行爲:+= |
__isub__(self, other) | 定義賦值減法的行爲:-= |
__imul__(self, other) | 定義賦值乘法的行爲:*= |
__itruediv__(self, other) | 定義賦值真除法的行爲:/= |
__ifloordiv__(self, other) | 定義賦值整數除法的行爲://= |
__imod__(self, other) | 定義賦值取模算法的行爲:%= |
__ipow__(self, other[, modulo]) | 定義賦值冪運算的行爲:**= |
__ilshift__(self, other) | 定義賦值按位左移位的行爲:<<= |
__irshift__(self, other) | 定義賦值按位右移位的行爲:>>= |
__iand__(self, other) | 定義賦值按位與操做的行爲:&= |
__ixor__(self, other) | 定義賦值按位異或操做的行爲:^= |
__ior__(self, other) | 定義賦值按位或操做的行爲: |
魔術方法 | 含義 |
---|---|
__pos__(self) | 定義正號的行爲:+x |
__neg__(self) | 定義負號的行爲:-x |
__abs__(self) | 定義當被 abs() 調用時的行爲 |
__invert__(self) | 定義按位求反的行爲:~x |
魔術方法 | 含義 |
---|---|
__complex__(self) | 定義當被 complex() 調用時的行爲(須要返回恰當的值) |
__int__(self) | 定義當被 int() 調用時的行爲(須要返回恰當的值) |
__float__(self) | 定義當被 float() 調用時的行爲(須要返回恰當的值) |
__round__(self[, n]) | 定義當被 round() 調用時的行爲(須要返回恰當的值) |
__index__(self) | 1. 當對象是被應用在切片表達式中時,實現整形強制轉換 2. 若是你定義了一個可能在切片時用到的定製的數值型,你應該定義 __index__ 3. 若是 __index__ 被定義,則 __int__ 也須要被定義,且返回相同的值 |
魔術方法 | 含義 |
---|---|
__enter__(self) | 1. 定義當使用 with 語句時的初始化行爲 2. __enter__ 的返回值被 with 語句的目標或者 as 後的名字綁定 |
__exit__(self, exc_type, exc_value, traceback) | 1. 定義當一個代碼塊被執行或者終止後上下文管理器應該作什麼 2. 通常被用來處理異常,清除工做或者作一些代碼塊執行完畢以後的平常工做 |
魔術方法 | 含義 |
---|---|
__len__(self) | 定義當被 len() 調用時的行爲(返回容器中元素的個數) |
__getitem__(self, key) | 定義獲取容器中指定元素的行爲,至關於 self[key] |
__setitem__(self, key, value) | 定義設置容器中指定元素的行爲,至關於 self[key] = value |
__delitem__(self, key) | 定義刪除容器中指定元素的行爲,至關於 del self[key] |
__iter__(self) | 定義當迭代容器中的元素的行爲 |
__reversed__(self) | 定義當被 reversed() 調用時的行爲 |
__contains__(self, item) | 定義當使用成員測試運算符(in 或 not in)時的行爲 |
上面基本上是Python中類的全部魔術方法了,下面針對一些重要的經常使用的方法進行說明。ssh
方法 | 意義 |
---|---|
__dir__() | 返回類或者對象的全部成員的名稱列表 dir()函數操做實例調用的就是__dir__() |
當dir(obj)時,obj的__dir__()方法被調用,若是當前實例不存在該方法,則按照mro開始查找,若是父類都沒有定義,那麼最終會找到object.__dir__()方法,該方法會最大程度的收集屬性信息。函數
class A: def __dir__(self): return 'ab' class B(A): pass print(dir(A)) # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] print(dir(B())) # ['a', 'b']
dir(obj)對於不一樣類型的對象obj具備不一樣的行爲:性能
必須可迭代
)組成列表返回
。globals()
結果相同)locals()
結果相同)在方法中,返回本地做用域的變量名(和locals()
結果相同)測試
locals()運行在全局時,結果和globals()相同設計
方法 | 含義 |
---|---|
__new__ | 實例化一個對象 該方法須要返回一個值,若是該值不是cls的實現,則不會調用__init__ 該方法永遠都是靜態方法 |
class A: def __new__(cls, *args, **kwargs): print(cls) # <class '__main__.A'> print(args) # ('daxin',) print(kwargs) # {'age': 20} def __init__(self, name, age): self.name = name self.age = age daxin = A('daxin', age=20) print(daxin.name) # 'NoneType' object has no attribute 'name'
分析:code
class A(object): def __new__(cls, *args, **kwargs): print(cls) # <class '__main__.A'> print(args) # ('daxin',) print(kwargs) # {'age': 20} return super().__new__(cls) # 只須要傳遞cls便可 def __init__(self, name, age): self.name = name self.age = age daxin = A('daxin', 20) print(daxin) print(daxin.name) # 'NoneType' object has no attribute 'name' print(daxin.age) # 'NoneType' object has no attribute 'name'
注意:__new__方法不多用,即便建立了該方法,也會使用return super().__new__(cls),調用基類objct的__new__方法來建立實例並返回。除非使用元類編程。
方法 | 意義 |
---|---|
__hash__ | 內建函數hash()調用的返回值,返回一個整數。若是定義這個方法該類的實例就可hash。 |
__eq__ | 對應==操做符,判斷2個對象是否相等,返回bool值 |
class A: def __hash__(self): return 123 # 返回值必須是數字 a = A() print(hash(a)) # 123
通常來講提供__hash__方法是爲了做爲set或者dict的key的。可是key是不重複的,因此怎麼去重呢?
前面咱們知道,set類型是不容許有重複數據的,那麼它是怎麼去重的呢?咱們說每一個元素的hash值都不相同,是經過hash值來去重的嗎?
class A: def __init__(self, name): self.name = name def __hash__(self): return 123 # 返回值必須是數字 def __repr__(self): # 爲了打印方便,這裏使用repr定義類型輸出格式 return '<{}>'.format(self.name) a = A('daxin') b = A('daxin') s = {a,b} print(s) # {<daxin>, <daxin>}
hash值相同的狀況下,從結果看並無去重。因此,去重,並非只看hash值,還須要使用__eq__來判斷2個對象是否相等。hash值相等,只是說明hash衝突,並不能說明兩個對象是相等的。判斷內容是否相等通常使用的是 == , 使用 == 進行比較時,就會觸發對象的__eq__方法。因此咱們來測試一下。
class A: def __init__(self, name): self.name = name def __hash__(self): return 123 # 返回值必須是數字 def __repr__(self): # 爲了打印方便,這裏使用repr定義類型輸出格式 return '<{} {}>'.format(self.name, id(self)) def __eq__(self, other): # self是實例自己,other就是等式右邊的對象 return True # 衡返回True,就表示只要進行比較就相等 a = A('daxin') b = A('daxin') print(a,b) # <daxin 2621275933832> <daxin 2621275933888> print({a, b}) # {<daxin 2621275933832>}
因此:去重的條件,首先判斷的是hash值,hash值相同的狀況下,判斷內容是否相等,確認是不是同一個元素。因此__eq__方法就很重要了。
def __eq__(self, other): return id(self) == id(other)
注意:
若是要去重,還須要配合__hash__方法,才能夠變爲可hash對象。
判斷是不是不可hash對象,可使用:isinstance(a, collection.Hashable),False表示不可hash。
想一下爲何list對象不可hash? 來看一下List的原碼
... __hash__ = None ...
在list內部,它把__hash__屬性置爲None了,學到一招,因此若是一個對象,不能被hash,那就把它置爲None把。
設計二維座標類Ponit, 使其成爲可hash類型,並比較2個座標的實例,是否相等?
class Point: def __init__(self, x, y): self.x = x self.y = y def __hash__(self): return hash(self.x) + hash(self.y) # 使用+拼接有很大概率相同,這裏可使用 return hash((self.x, self.y)) def __eq__(self, other): return self.x == other.x and self.y == other.y # 若是id相同,那麼就不須要再比較屬性了,由於確定是同一個數據 # return self is other or (self.x == other.x and self.y == other.y) def __repr__(self): return '{} {} {}'.format(self.x, self.y, id(self)) a = Point(3, 4) b = Point(3, '4') print(set([a, b]))
方法 | 意義 |
---|---|
__bool__ | 內建函數bool(),或者被看成邏輯表達式時,調用這個__bool__函數的返回值。 若是沒有定義__bool__函數,那麼就會尋找__len__返回長度,非0爲真。 若是__len__()也沒有定義,那麼全部實例都返回真 |
即:使用bool時,先判斷__bool__,而後再判斷__len__,不然True
class A: def __bool__(self): return False print(bool(A())) # False class A: def __len__(self): # 只有len return 1 print(bool(A())) # True
__len__的返回值必須爲大於0的整數。
可視化就是指實例的表現形式,好比print的時候實例如何顯示,被看成參數傳遞時又該如何顯示
方法 | 意義 |
---|---|
__str__ | str、format、print函數調用,須要返回對象字符串表達式。 若是沒有定義,就去調用__repr__方法,返回字符串表達。 若是__repr__沒有定義,就直接返回對象的內存地址信息。 |
__repr__ | 內建函數repr()對一個對象獲取字符串表達式。 調用__repr__方法返回字符串表達式,若是__repr__也沒有定義,就直接返回object的定義的(就是內存地址) |
__bytes__ | bytes()函數調用,返回一個對象的bytes表達,即返回bytes對象。 |
class A: def __init__(self): self.name = 'daxin' def __str__(self): return '{} from str method'.format(self.name) # 必須返回字符串 daxin = A() print(daxin) # daxin from str method print([daxin,daxin]) # [<__main__.A object at 0x00000207946A8EB8>, <__main__.A object at 0x00000207946A8EB8>] class A: def __init__(self): self.name = 'daxin' def __repr__(self): return '{} from repr method'.format(self.name) # 必須返回字符串 daxin = A() print(daxin) # daxin from repr method print([daxin,daxin]) # [daxin from repr method, daxin from repr method] class A: def __init__(self): self.name = 'daxin' def __bytes__(self): return self.name.encode('utf-8') # 必須是一個bytes對象 daxin = A() print(bytes(daxin)) # b'daxin'
不能經過判斷是否在引號來判斷輸出值的類型,類型判斷要使用type和instance。
用於使咱們本身定義的類支持運算符的操做。好比加減乘除這類運算符,須要注意的是通常的操做數都爲2個,符號左邊的稱之爲左實例,而右邊的成爲右實例。
當使用左實例減右實例時,會觸發左實例的__sub__方法(若是左實例不存在__sub__方法,則會執行右實例的__rsub__方法)
class A: def __init__(self,value): self.value = value def __sub__(self, other): return self.value - other.value class B: def __init__(self,value): self.value = value def __rsub__(self, other): return 100 a=A(10) b=B(5) print(a-b) # 執行a的__sub__方法,若是沒有就執行b的__rsub__方法。
當使用-=(減等)觸發的是__isub__方法了,若是沒有定義__isub__方法,那麼最後調用__sub__方法。
class A: def __init__(self,value): self.value = value def __isub__(self, other): return self.value - other.value a = A(13) b = A(5) a -= b print(a, type(a)) # 8 <class 'int'>
注意:
完成座標軸設計,實現向量的相等判斷,以及加法運算
class Ponit: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): return True if id(self) == id(other) else (self.x == other.x and self.y == other.y) def __add__(self, other): # return self.x + other.x,self.y + other.y # 直接返回(2,4) return self.__class__(self.x + other.x,self.y + other.y) # 返回一個新的實例 def __iadd__(self, other): self.x = self.x + other.x self.y = self.y + other.y return self def __str__(self): return '<{},{}>'.format(self.x,self.y) a = Ponit(1,2) b = Ponit(1,2) print(a==b) # True a += b # 調用a的__iadd__方法,原地修改 print(a) # <2,4> c = a + b # 調用a的__add__方法,能夠返回結果,也能夠返回一個新的對象,看本身需求 print(c) # <3,6> # 注意 a = Point(1,2) b = Point(3,4) c = Point(5,6) d = a + b + c # 之因此能夠這樣一直加,是由於__add__方法,返回了一個新的實例。執行+發時,調用的就是實例的__add__方法 print(d)
當咱們使用面向對象定義的類,須要大量的運算時,可使用這種運算符重載的方式編寫,由於這中運算符是數學上最多見的表達方式。上面的例子就實現了Point類的二元操做,從新定義了Point + Point 甚至 Point + Point + Ponit。
int類,幾乎實現了全部操做符。
__lt__,__le__,__eq__,__ne__,__gt__,__ge__等是大小比較經常使用的方法,可是所有寫完比較麻煩,使用functools模塊提供的total_ordering裝飾器就是能夠簡化代碼
可是要求__eq__必須實現,其餘方法__lt__,__le__,__gt__,__ge__,實現其一。
import functools @functools.total_ordering class Person: def __init__(self,name,age): self.name = name self.age =age def __eq__(self, other): return self.age == other.age def __gt__(self, other): return self.age > other.age daxin = Person('daxin',99) dachenzi = Person('dachenzi',66) print(daxin == dachenzi) # False print(daxin < dachenzi) # False print(daxin > dachenzi) # True print(daxin >= dachenzi) # True print(daxin <= dachenzi) # False print(daxin != dachenzi) # True
雖然簡化了不少代碼,可是通常來講實現等於或者小於方法也就夠了,其餘的能夠不實現,這個裝飾器只是看着很美好,且可能會帶來性能問題,建議須要用到什麼方法就本身建立,少用這個裝飾器。
class Person: def __init__(self,name,age): self.name = name self.age =age def __eq__(self, other): return self.age == other.age def __gt__(self, other): return self.age > other.age def __ge__(self, other): return self.age >= other.age daxin = Person('daxin',99) dachenzi = Person('dachenzi',66) print(daxin == dachenzi) # False print(daxin < dachenzi) # False print(daxin > dachenzi) # True print(daxin >= dachenzi) # True print(daxin <= dachenzi) # False print(daxin != dachenzi) # True
爲何這樣寫能夠的呢?想一下:
僅僅添加了一個__ge__方法,就完成了需求,因此仍是建議本身寫吧。
方法 | 意義 |
---|---|
__len__ | 內建函數len(),返回對象的長度(>=0的整數) 若是把對象看成容器類型看,就如同list或者dict。在bool()函數調用的時候,若是對象沒有__bool__()方法,就會看__len__()方法是否存在,返回非0時,表示真 |
__iter__ | 迭代容器時,調用,返回一個新的迭代器對象 。 |
__contains__ | in成員操做符,沒有實現,就用__iter__犯法遍歷。 |
__getitem__ | 實現self[key]方式的訪問,對於list對象,key爲index,對於dict來講,key爲hashable,key不存在引起KeyError異常 |
__setitem__ | 和__getitem__的訪問相似,是設置值時調用的方法。 |
__missing__ | 字典和其子類使用__getitm__()調用時,key不存在執行該方法。 |
class Contain: def __init__(self): self.items = [] def __iter__(self): # return (item for item in self.items) # 生成器是一個特殊的迭代器 return iter(self.items) # 或者直接包裝新的迭代器 def __contains__(self, value): for item in self.items: if item == value: return True else: return False def __getitem__(self, index): return self.items[index] def __setitem__(self, key, value): self.items[key] = value def __add__(self, other): self.items.append(other) return self def __str__(self): return '{}'.format(id(self)) __repr__ = __str__ c = Contain() d = Contain() e = Contain() c + d + e print(c.items) print(c[1]) c[1] = e for i in c: print(i)
Python中一切皆對象,函數也不例外,函數名加上(),就表示調用函數對象的__call__方法
def func(): print(func.__name__,func.__module__) func() # func __main__ 等於 func().__call__()
方法 | 含義 |
---|---|
__call__ | 類中定義一個該方法,實例就能夠像函數同樣調用 |
class Call: def __call__(self, *args, **kwargs): return 'hello world' a = Call() print(a()) # 調用c.__call__方法
定義一個斐波那契數列的類,方便調用計算第N項,增長迭代方法,返回容器長度,支持索引方法.
class Fib: def __init__(self): self.items = [0,1,1] def __iter__(self): return iter(self.items) def __len__(self): return len(self.items) def __getitem__(self, index): length = len(self.items) # 3 4 if index < 0: raise KeyError if index >= length: for i in range(length,index+1): # 3,5 self.items.append(self.items[i-1] + self.items[i-2]) return self.items[index] def __call__(self, index): return self.__getitem__(index) # return self[index] # 這裏調用直接經過key的方式訪問元素,其實仍是會調用__getitem__方法。self[index] == self.__getitem__(index) f = Fib() print(f[101]) print(f(101))