魔法方法及其作用
在Python中的內置類型都支持一些統(tǒng)一的函數(shù)接口工三,比如len, print, bool
等架谎,這些函數(shù)是如何實現(xiàn)的呢诸狭?當我們建立自己的數(shù)據(jù)類型時券膀,如何表現(xiàn)的很pythonic,也就是讓我們自己的數(shù)據(jù)類型也能支持這些操作呢驯遇?
在Python中有一些以雙下劃線開頭和結尾的方法芹彬,叫做魔法方法,這就是實現(xiàn)這些統(tǒng)一接口的關鍵叉庐。
例如我們想生成一個Classmate
類來保存一個班級的名單舒帮,想讓len()
能夠取到里面保存的人數(shù),想讓print()
能夠打印出名單,想讓bool()
返回名單是否為空玩郊,那么如何組織我們的類肢执,讓我們可以像使用python內置類型一樣使用它呢?
_no_value = object()
class Classmate(object):
def __init__(self, name_lst=_no_value):
if name_lst == _no_value:
self._name_lst = list()
else:
self._name_lst = list(name_lst) # 建立拷貝译红,防止改動傳入數(shù)據(jù)
def __len__(self):
return len(self._name_lst)
def __repr__(self):
return str(self._name_lst)
def __bool__(self):
return len(self._name_lst) != 0 # 其實可以不寫预茄,python會自動調用__len__
if __name__ == '__main__':
c = Classmate(['Xiao Zhang', 'Lao Wang', 'Xiao Zhao'])
print(c) # ['Xiao Zhang', 'Lao Wang', 'Xiao Zhao']
print(bool(c)) # True
print(len(c)) # 3
在上面的例子中,我們可以看到侦厚,盡管Classmate
并非python中內置的類耻陕,我們仍然可以通過實現(xiàn)魔法方法,將它的使用方式和內置類的使用方式統(tǒng)一起來假夺。
常用魔法方法總結
下面總結一下python中常用的魔法方法淮蜈,并給出使用的例子
二元操作符
+ object.__add__(self, other)
- object.__sub__(self, other)
* object.__mul__(self, other)
// object.__floordiv__(self, other)
/ object.__div__(self, other)
% object.__mod__(self, other)
** object.__pow__(self, other[, modulo])
<< object.__lshift__(self, other)
>> object.__rshift__(self, other)
& object.__and__(self, other)
^ object.__xor__(self, other)
| object.__or__(self, other)
我們用一個二維向量的例子來演示其中幾個方法的作用:
class Vector(object):
def __init__(self, vx, vy):
self._vx = vx
self._vy = vy
def __add__(self, other):
return Vector(self._vx + other.get_x(), self._vy + other.get_y())
def __sub__(self, other):
return Vector(self._vx - other.get_x(), self._vy - other.get_y())
def __pow__(self, power, modulo=None):
return Vector(pow(self._vx, power), pow(self._vy, power))
def __repr__(self):
return f"Vector({self._vx}, {self._vy})"
def get_x(self):
return self._vx
def get_y(self):
return self._vy
if __name__ == '__main__':
v1 = Vector(3.0, 5.0)
v2 = Vector(-1.0, 2.0)
print(v1 + v2) # Vector(2.0, 7.0)
print(v1 - v2) # Vector(2.0, 7.0)
print(v1 ** 2) # Vector(9.0, 25.0)
可以看到在定義了__add__, __sub__, __pow__
之后,我們自定義的Vector
類也可以像python中內置的int, float
類型一樣已卷,進行相加梧田、相減、冪次操作了侧蘸。需要注意的是裁眯,在完成二元操作符對應的魔法方法時,我們需要返回一個相同類型的對象讳癌,這是因為我們需要考慮使用者進行連續(xù)加穿稳、連續(xù)減等操作的可能性。
擴展二元操作符
+= object.__iadd__(self, other)
-= object.__isub__(self, other)
*= object.__imul__(self, other)
/= object.__idiv__(self, other)
//= object.__ifloordiv__(self, other)
%= object.__imod__(self, other)
**= object.__ipow__(self, other[, modulo])
<<= object.__ilshift__(self, other)
>>= object.__irshift__(self, other)
&= object.__iand__(self, other)
^= object.__ixor__(self, other)
|= object.__ior__(self, other)
擴展二元操作符的使用方法和二元操作符基本一致:
class Vector(object):
def __init__(self, vx, vy):
self._vx = vx
self._vy = vy
def __add__(self, other):
return Vector(self._vx + other.get_x(), self._vy + other.get_y())
def __iadd__(self, other):
return Vector(self._vx + other.get_x(), self._vy + other.get_y())
def __sub__(self, other):
return Vector(self._vx - other.get_x(), self._vy - other.get_y())
def __pow__(self, power, modulo=None):
return Vector(pow(self._vx, power), pow(self._vy, power))
def __repr__(self):
return f"Vector({self._vx}, {self._vy})"
def get_x(self):
return self._vx
def get_y(self):
return self._vy
if __name__ == '__main__':
v1 = Vector(3.0, 5.0)
v2 = Vector(-1.0, 2.0)
v1 += v2
print(v1) # Vector(2.0, 7.0)
在定義了__iadd__
方法之后晌坤,+=
也可以被用于我們的類了
一元操作符
- object.__neg__(self)
+ object.__pos__(self)
abs() object.__abs__(self)
~ object.__invert__(self)
complex() object.__complex__(self)
int() object.__int__(self)
long() object.__long__(self)
float() object.__float__(self)
oct() object.__oct__(self)
hex() object.__hex__(self)
round() object.__round__(self, n)
floor() object__floor__(self)
ceil() object.__ceil__(self)
trunc() object.__trunc__(self)
比較函數(shù)
< object.__lt__(self, other)
<= object.__le__(self, other)
== object.__eq__(self, other)
!= object.__ne__(self, other)
>= object.__ge__(self, other)
> object.__gt__(self, other)
類的表示與輸出
str() object.__str__(self)
repr() object.__repr__(self)
len() object.__len__(self)
hash() object.__hash__(self)
bool() object.__nonzero__(self)
dir() object.__dir__(self)
sys.getsizeof() object.__sizeof__(self)
__str__
方法與__repr__
方法
在類的表示中__str__
和__repr__
方法值得一提逢艘,這兩個方法在一定程度上是有重合的:
- 他們都提供了將對象轉化為某種字符串的方式
- 當對象沒有實現(xiàn)
__str__
方法時,會用__repr__
方法替代
對于他們的使用方式骤菠,可以簡單概括為:
- 如果只想要實現(xiàn)其中之一它改,那么實現(xiàn)
__repr__
- 如果想要輸出的結果可讀性更強,那么可以選擇去實現(xiàn)
__str__
我們可以看一下下面的例子:
class Vector(object):
def __init__(self, vx, vy):
self._vx = vx
self._vy = vy
def __repr__(self):
return f"Repr Vector({self._vx}, {self._vy})"
def __str__(self):
return f"Str Vector({self._vx}, {self._vy})"
if __name__ == '__main__':
v = Vector(3.0, 5.0)
print(v) # Vector({self._vx}, {self._vy})
print("%s" % v) # Str Vector(3.0, 5.0)
print("%r" % v) # Vector({self._vx}, {self._vy})
print(str(v)) # Vector({self._vx}, {self._vy})
如果我們注釋掉__repr__
方法商乎,得到的結果為:
Str Vector(3.0, 5.0)
Str Vector(3.0, 5.0)
<__main__.Vector object at 0x10c4e0eb8>
Str Vector(3.0, 5.0)
如果我們注釋掉__str__
方法央拖,得到的結果為:
Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)
對比第二行的結果,說明了當沒有__str__
方法時鹉戚,會調用__repr__
的結果鲜戒,但是當沒有實現(xiàn)__repr__
方法時,則會用默認的__repr__
輸出類似return "%s(%r)" % (self.__class__, self.__dict__)
的結果抹凳。
類容器實現(xiàn)
類容器的實現(xiàn)方法告訴編譯器我們的類將執(zhí)行迭代遏餐、調用、索引等行為:
len() object.__len__(self)
self[key] object.__getitem__(self, key)
self[key] = value object.__setitem__(self, key, value)
從對象取切片 object.__getslice__(self, start, end)
為切片設置值 object.__setslice__(self, start, end, sequence)
刪除切片 object.__delslice__(self, start, end)
del[key] object.__delitem__(self, key)
iter() object.__iter__(self)
reversed() object.__reversed__(self)
in操作 object.__contains__(self, item)
字典key不存在時 object.__missing__(self, key)
__getitem__赢底、__setitem__和__delitem__
這三個方法用于從類容器中用鍵取值境输,設置了這三個函數(shù)之后蔗牡,就可以對類容器直接用鍵來取得颖系、修改和刪除里面的鍵值對嗅剖。例如:
class Classmate(object):
"""A demo class which stores some name-age pairs"""
def __init__(self, **kwargs):
self._info = kwargs
def __str__(self):
return str(self._info)
def __getitem__(self, item):
print("__getitem__")
if item in self._info:
return self._info[item]
else:
print("Name not found")
def __setitem__(self, key, value):
print("__setitem__")
self._info[key] = value
def __delitem__(self, key):
print("__delitem__")
if key in self._info:
del self._info[key]
c = Classmate(laowang=50, xiaohuang=18, xiaoli=17, laojin=66)
print(c["laowang"]) # 調用__getitem__
c["xinren"] = 20 # 調用__setitem__
del c["xiaohuang"] # 調用__delitem__
print(c)
同時,在python3中嘁扼,將python2對容器進行切片操作的魔法方法__getslice__信粮、__setslice__和__delslice__
也整合到了這三個方法當中。如下例:
class someClass(object):
""" This is description for the class"""
def __init__(self, startVal, endVal):
if endVal > startVal:
self._lst = list(range(startVal, endVal))
else:
self._lst = list(range(0, 10))
def __getitem__(self, index):
print("__getitem__")
if isinstance(index, slice):
print(self._lst[index.start:index.stop:index.step])
def __setitem__(self, index, value):
print("__setitem__")
if isinstance(index, slice):
self._lst[index.start:index.stop:index.step] = value
def __delitem__(self, index):
print("__delitem__")
if isinstance(index, slice):
del self._lst[index.start:index.stop:index.step]
def __str__(self):
return str(self._lst)
c = someClass(5, 10)
print(c)
print(c[1:3])
c[1:3] = [10, 11]
print(c)
del c[1:3]
print(c)