collections.namedtuplehtml
__getitem__ 和 __len__java
__repr__和__str__python
__abs__、__add__和__mul__git
__bool__github
文中代碼均放在github上:https://github.com/ampeeg/cnblogs/tree/master/python高級數組
語 法 | 調用的方法(按照順序尋找) | 備註 |
list[2] | __getitem__(2) | |
list[1:3:2] | __getitem__(slice(1,3,2)) | 切片時傳入的參數是slice類型 |
for i in object: | __iter__()、__getitem__() | __iter__須要返回迭代器,並不斷調用next() |
in object | __contains__()、__iter__()、__getitem__() | __iter__()、__getitem__()會按照順序搜索 |
print(object) | __str__()、__repr__() | |
if object: | __bool__()、__len__() | 使用if、while等判斷句時,會調用__bool__()less 若是沒有這兩個方法,通常狀況下,自定義的類總認爲是真的ide |
本文所指的python數據模型,也可成爲python中內置的對象模型(一切皆爲對象),其包含的一些方法爲特殊方法,在java中也稱「魔術方法」。因爲python文檔裏面喜歡使用「數據模型」這個詞,因此本文依此稱數據模型。函數
簡單來講,數據模型就是python自有的數據類型,及其包含的特殊方法。例如:使用len()時會調用__len__特殊方法;使用list[]時會調用__getitem__方法;使用各種運算符也會調用其相對應的方法。從根本上而言,list[]、+、-、*、/、for i in x這些寫法只是爲了更簡潔和更具備可讀性,但內部跟其餘操做一下,也是經過方法實現的,這就是特殊方法。
# 導入可命名元組 from collections import namedtuple # 建立的兩種方法 (建立股票模型,每隻股票包括name和price) Stock_1 = namedtuple("stock", ("name", "price")) # 方法1:第二個參數傳入可迭代對象(元組、數組等均可) Stock_2 = namedtuple("stock", "name price") # 方法2:字符串之間用空格隔開 # 生成多隻股票 stock01 = Stock_1("SH000001", 1) stock02 = Stock_1("SH000002", 12) stock03 = Stock_1("SH000003", 123) stock04 = Stock_1("SH000004", 1234) # 訪問股票信息 print(stock01.name) # 屬性形式 SH000001 print(stock04[1]) # 列表形式 1234 |
一、__len__
class Foo: def __len__(self): # 重寫__len__方法 print("method __len__") return 1 if __name__ == "__main__": foo = Foo() n = len(foo) # 使用len()時會自動調用__len__方法:method __len__ print(n) # 1 |
二、__getitem__
from collections import namedtuple Stock = namedtuple("stock", ["name", "price"]) class Foo: def __init__(self): self._stock = [Stock(name, price) for name, price in zip(range(1, 100), range(1, 100))] def __len__(self): return len(self._stock) def __getitem__(self, item): print(item) return self._stock[item] if __name__ == "__main__": foo = Foo() print(len(foo)) print(foo[3]) # 使用foo[3]時會調用__getitem__方法,解釋器會將3傳遞給__getitem__(self, item)中的item參數 # stock(name=4, price=4) print(foo[3:6]) # 使用切片操做時也會調用__getitem__方法,解釋器會傳遞slice(3, 6, None)item參數 # [stock(name=4, price=4), stock(name=5, price=5), stock(name=6, price=6)] |
重寫__getitem__後就可直接遍歷對象:
if __name__ == "__main__": # 此時可直接用for循環對foo進行遍歷 for i in foo: print(i) # 因爲實現了__getitem__方法,foo實例就變成了可迭代對象 # 不只可使用for循環正向迭代,也可反向迭代;還可使用in判斷 for i in reversed(foo): print(i) # 反向迭代 print(Stock(name=2, price=2) in foo) # in判斷會先調用__contains__方法,可是若是沒有該方法,則調用__getitem__按順序迭代搜索 # True (調用了2次getitem) print(Stock(name=2, price=3) in foo) # False (調用了100次getitem方法,最後一次foo[99]發現不存在而中止迭代) |
三、繼續說說for i in x: 語句
剛剛咱們使用for i in foo時發現能夠正常迭代,若是在Foo類中重寫__iter__方法,則沒法正確迭代了:
from collections import namedtuple Stock = namedtuple("stock", ["name", "price"]) class Foo: def __init__(self): self._stock = [Stock(name, price) for name, price in zip(range(1, 100), range(1, 100))] def __len__(self): return len(self._stock) def __getitem__(self, item): print(item) return self._stock[item] def __iter__(self): pass if __name__ == "__main__": foo = Foo() for i in foo: # 報錯:TypeError: iter() returned non-iterator of type 'NoneType' print(i) |
若是咱們把以上__iter__方法改爲以下,那麼又可以使用for語句了:
def __iter__(self): return iter(self._stock)
事實上咱們在使用for i in foo語句時,會先調用__iter__方法,返回一個迭代器,而後for循環會不斷使用next()進行遍歷;若是foo裏面沒有該方法,則會調用__getitem__,並會從0開始依次讀取相應的下標,直到發生IndexError爲止,這是一種舊的迭代協議。
一樣的,使用in判斷時,解釋器會依次尋找__contains__、__iter__、__getitem__方法。
from collections import namedtuple Stock = namedtuple("stock", ["name", "price"]) class Foo: def __init__(self): self._stock = [Stock(name, price) for name, price in zip(range(1, 100), range(1, 100))] def __len__(self): return len(self._stock) def __getitem__(self, item): print(item) return self._stock[item] def __iter__(self): return iter(self._stock) # def __contains__(self, item): # print(item) # return False if __name__ == "__main__": foo = Foo() for i in foo: # 重寫了__iter__(self)後解釋器自動執行iter(foo) print(i) x = iter(foo) # 手動執行 print(next(x)) # stock(name=1, price=1) print(next(x)) # stock(name=2, price=2) print(next(x)) # stock(name=3, price=3) print(Stock(name=4, price=4) in foo) # 按照__contains__、__iter__、__getitem__順序尋找:True |
# 接下來的例子引用自《流暢的python》 # 建立一個二維向量的類Vector,慢慢給它添加一些運算 class Vector: def __init__(self, x=0, y=0): self.x = x self.y = y def __repr__(self): return 'repr: Vector(%r, %r)' % (self.x, self.y) # def __str__(self): # 若是類中同時有__str__和__repr__,則調用print是會先使用__str__ # return "str: Vector(%r, %r)" % (self.x, self.y) # 這個類中如今只實現了__repr__方法 if __name__ == "__main__": v = Vector(2, 3) print(v) # 此時打印出來的不是<Vector object at 0x0000003>這種形式 # 打印出來的是Vector(2, 3) # 若是類中實現了__str__一樣有此做用 # __repr__和__str__的區別在於,後者是在str()函數中被使用,或是在用print打印函數打印一個對象的時候才被 # 調用。若是你只想實現這兩個特殊方法中的一個,__repr__是更好的選擇,由於若是一個對象沒有__str__函數 # 而python又須要調用它的時候,解釋器會用__repr__做爲代替 # 故使用print()函數時,解釋器會按照__str__、__repr__的順序尋找 |
# 接上面的二維向量的例子 from math import hypot class Vector: def __init__(self, x=0, y=0): self.x = x self.y = y def __repr__(self): return 'Vector(%r, %r)' % (self.x, self.y) def __abs__(self): # abs原本是絕對值,在二維向量中指模 return hypot(self.x, self.y) def __add__(self, other): x = self.x + other.x y = self.y + other.y return Vector(x, y) def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar) if __name__ == "__main__": v = Vector(4, 3) # 使用abs()求模,解釋器自動調用__abs__方法 print(abs(v)) # 5.0 # 使用+求向量加法,解釋器自動調用__add__方法 v2 = Vector(1, 5) print(v + v2) # Vector(5, 8) # ps: __add__方法返回的是Vector對象,而後print函數會調用__repr__ # 使用*求向量與數的乘法,解釋器自動調用__mul__方法 print(v * 3) # Vector(12, 9) # 這裏只實現了向量的數乘, 而且未實現 3*v |
# 繼續在上面列子中添加__bool__ from math import hypot class Vector: def __init__(self, x=0, y=0): self.x = x self.y = y def __repr__(self): return 'Vector(%r, %r)' % (self.x, self.y) def __abs__(self): return hypot(self.x, self.y) def __bool__(self): return bool(abs(self)) if __name__ == "__main__": v = Vector(0, 3) if v: # 調用__bool__ print(abs(v)) # 3.0 # 使用if或while語句,或者and\or\not運算符,爲了斷定一個對象v是真仍是假,python會調用bool(v),這個函數只能返回True或者False # 默認狀況下,自定義的類的實例總被認爲是真的,除非這個類對__bool__或者__len__函數有本身的實現。 # bool(v)後面是調用v.__bool__()的結果;若是不存在__bool__方法,那麼bool(v)會嘗試調用v.__len__(),若返回0,則bool返回False,不然爲True # python 3.6的官方文檔以下介紹 ''' By default, an object is considered true unless its class defines either a __bool__() method that returns False or a __len__() method that returns zero, when called with the object. Here are most of the built-in objects considered false: constants defined to be false: None and False. zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1) empty sequences and collections: '', (), [], {}, set(), range(0) Operations and built-in functions that have a Boolean result always return 0 or False for false and 1 or True for true, unless otherwise stated. (Important exception: the Boolean operations or and and always return one of their operands.) ''' |
本部份內容能夠參考官方網站 https://docs.python.org/3/reference/datamodel.html#special-method-names
python中一共有83個特殊方法,其中47個用於算術運算、位運算和比較操做。我根據《流暢的python》中的整理,摘錄以下兩個表格
表1:跟運算符無關的特殊方法
類 別 | 方法名 |
字符串/字節序列表示形式 | __repr__、__str__、__format__、__bytes__ |
數值轉換 | __abs__、__bool__、__complex__、__int__、__float__、__hash__、__index__ |
集合模擬 | __len__、__getitem__、__setitem__、__delitem__、__contains__ |
迭代枚舉 | __iter__、__reversed__、__next__ |
可調用模擬 | __call__ |
上下文管理 | __enter__、__exit__ |
實例建立和銷燬 | __new__、__init__、__del__ |
屬性管理 | __getattr__、__getattribute__、__setattr__、__delattr__、__dir__ |
屬性描述符 | __get__、__set__、__delete__ |
跟類相關的服務 | __prepare__、__instancecheck__、__subclasscheck__ |
表2:跟運算符相關的特殊方法
類 別 | 方法名和對應的運算符 |
一元運算符 | __neg__ -、__pos__ +、__abs__ abs() |
衆多比較運算符 | __lt__ <、__le__ <=、__eq__ ==、__ne__ !=、__gt__ >、__ge__>= |
算數運算符 | __add__ +、__sub__ -、__mul__ *、__truediv__ /、__floordiv__ //、 __mod__ %、__divmod__ divmod()、__pow__ **或pow()、__round__ round() |
反向算數運算符 | __radd__、__rsub__、__rmul__、__rtruediv__、__rfloordiv__、__rmod__、__rdivmod__、__rpow__ |
增量賦值算術運算符 | __iadd__、__isub__、__imul__、__itruediv__、__ifloordiv__、__imod__、__ipow__ |
位運算符 | __invert__ ~、__lshift__ <<、__rshift__ >>、__and__ &、__or__ |、__xor__ ^ |
反向位運算符 | __rlshift__、__rrshift__、__rand__、__rxor__、__ror__ |
增量賦值位運算符 | __ilshift__、__irshift__、__iand__、__ixor__、__ior__ |
一、特殊方法的調用是隱式的,一般你的代碼無需直接使用特殊方法。除非有大量的元編程存在,直接調用特殊方法的頻率應該遠遠低於你去實現它們的次數。惟一的例外多是__init__方法,你的代碼裏可能常常會用到它,目的是在你的子類的__init__方法中調用超類的構造器。
二、經過內置的函數(例如len、iter、str等)來使用特殊方法是最好的選擇。這些內置函數不只會調用特殊方法,一般還提供額外的好處,並且對於內置的類來講,它們的速度更快。
三、不要本身想固然地隨意添加特殊方法,好比__foo__之類的,由於雖然如今這個名字沒有被python內部使用,之後就不必定了。
——《流暢的Python》