python高級(一)—— python數據模型(特殊方法)


本文主要內容

  collections.namedtuple

      __getitem__ 和 __len__

  __repr__和__str__

  __abs__、__add__和__mul__

  __bool__

 

python高級——目錄

  文中代碼均放在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__()

如果沒有這兩個方法,一般情況下,自定義的類總認為是真的

 

何為python的數據模型

  

  本文所指的python數據模型,也可成為python中內置的對象模型(一切皆為對象),其包含的一些方法為特殊方法,在java中也稱“魔術方法”。由於python文檔里面喜歡使用“數據模型”這個詞,所以本文依此稱數據模型。

  

  簡單來說,數據模型就是python自有的數據類型,及其包含的特殊方法。例如:使用len()時會調用__len__特殊方法;使用list[]時會調用__getitem__方法;使用各類運算符也會調用其相對應的方法。從根本上而言,list[]、+、-、*、/、for i in x這些寫法只是為了更簡潔和更具有可讀性,但內部跟其他操作一下,也是通過方法實現的,這就是特殊方法。

 

可命名元組(namedtuple)

# 導入可命名元組
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

 

 __getitem__ 和 __len__ 

 

  1、__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

 

  2、__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]發現不存在而停止迭代)

 

  3、繼續說說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

 

__repr__和__str__

# 接下來的例子引用自《流暢的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__的順序尋找

 

  

__abs__、__add__和__mul__

# 接上面的二維向量的例子

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__

# 繼續在上面列子中添加__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.)
'''

 

python中的全部特殊方法

 

  本部分內容可以參考官方網站 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__

 

如何使用特殊方法:

 

1、特殊方法的調用是隱式的,通常你的代碼無需直接使用特殊方法。除非有大量的元編程存在,直接調用特殊方法的頻率應該遠遠低於你去實現它們的次數。唯一的例外可能是__init__方法,你的代碼里可能經常會用到它,目的是在你的子類的__init__方法中調用超類的構造器。

2、通過內置的函數(例如len、iter、str等)來使用特殊方法是最好的選擇。這些內置函數不僅會調用特殊方法,通常還提供額外的好處,而且對於內置的類來說,它們的速度更快。

3、不要自己想當然地隨意添加特殊方法,比如__foo__之類的,因為雖然現在這個名字沒有被python內部使用,以后就不一定了。

 

——《流暢的Python》

 

python高級系列文章目錄

python高級——目錄

 

                 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM