標籤:

第九章 符合Python風格的對象

得益於Python內置的數據模型,自定義類型的使用可以向內置類型那樣自然。這考的不是類的繼承,而是鴨子類型(duck typing):我們只需要按照需求為類實現相對應的方法即可!

本章我們要自己嘗試構造一個簡單完整的二維歐幾里得向量類型

這就需要以下的知識:

  • 對象的表現形式(如 repr、str )
  • 用於生成對象的其他表現形式的內置函數(如表示為位元組碼 bytes)
  • 實現只讀屬性
  • 將對象變成可散列的,以便在集合中作為dict使用
  • 利用 __ solt __ 特性節省內存

對象的表現形式

每門面向對象的編程語言都會提供一種獲取對象字元串表現形式的標準方式,Python提供了兩個方式:

  • repr() : 便於開發者理解的方式來返回對象的字元串形式
  • str() : 便於用戶理解到的方式來返回對象的字元串形式

因此我們再建立類的時候,要實現這兩個特殊方法來用於對象的字元串表現形式。

向量類的初步定義:

n歐幾里得向量類nnv1nnnnfrom array import arraynimport mathnnnclass Vector2d:n typecode = d # 類屬性,再實例和位元組序列之間轉換時會用到nn def __init__(self, x, y): # 將坐標轉換為浮點類型n self.x = float(x)n self.y = float(y)nn def __iter__(self): # 定義成可迭代類型,這樣方便我們拆包 如 x,y = my_vectorn return (i for i in (self.x, self.y))nn def __repr__(self): # {!r} 獲取各個分量的表現形式,最後構造成一個字元串n class_name = type(self).__name__n return {}({!r},{!r}).format(class_name, *self)nn def __str__(self): # 從self的可迭代特性中,輕鬆可以得到一個元組,用於表示此類n return str(tuple(self))nn def __bytes__(self): # 先將typocode轉換為位元組序列,再迭代實例得到一個數組之後,再將該數組轉換為位元組序列n return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))nn def __eq__(self, other): # 簡單的比較 有局限性 比如 Vector2d(3,4) == [3,4] 會返回Truen return tuple(self) == tuple(other)nn def __abs__(self): # 取模n return math.hypot(self.x, self.y)nn def __bool__(self): # 0:false 1:Truen return bool(abs(self))nn# testnn# 實例的分類可以通過屬性直接訪問nv1 = Vector2d(3, 4)nprint(v1.x, v1.y)nn# 實例可以拆包成元組nx, y = v1nprint(x, y)nprint(v1)nn# 通過eval函數,表明repr函數調用的實例得到的是對構造方法的準確表述nv1_clone = eval(repr(v1))nprint(v1 == v1_clone)nn# bytes函數會調用類的__bytes__方法來生成實例的二進位表現形式noctets = bytes(v1)nprint(octets)n# 測試一下abs和bool方法nprint(abs(v1))nprint(bool(v1),bool(Vector2d(0,0)))nnnnOUT:n3.0 4.0n3.0 4.0n(3.0, 4.0)nTruenbdx00x00x00x00x00x00x08@x00x00x00x00x00x00x10@n5.0nTrue Falsenn

到這裡我們通過定了許多Python內置的魔法方法,讓向量類逐漸(Python化)

但是很明顯,我們目前只能將該類實例表現為bytes,

並不能是使用bytes()函數生成的二進位表示,來重構向量類。

備選的構造方法

為了解決上面提出的問題,我們需要構造一個類方法(classmethod)

那麼我們再這裡就得說明一下類方法了:

classmethod 改變了調用方法的方式,因此類方法的第一個參數應該是類本身,而不是實例 (約定俗成第一個參數寫為cls)。

classmethod 的最常見的用途是定義備選構造方法

與classmethod類似的,還有staticmethod(靜態方法)

按照作者的理解,靜態方法其實就是普通的函數。只是再類的定義體中出現。

實際上,對於這個觀點有很多爭議,想要了解的小夥伴可以自己去查閱一下相關的資料。

說完類方法,我們來實現上述的功能:

class Vector2d:nn @classmethod # 類方法的裝飾器n def frombytes(cls, octets):n typecode = chr(octets[0]) # 從二進位碼里的第一位轉換為tyoecoden # 使用傳入的位元組序列構造一個memoryview,並使用typecode轉換n memv = memoryview(octets[1:]).cast(typecode)n return cls(*memv) # 拆包轉換後的參數,並返回構造後的實例nn .....省略下面的代碼.....n# testnb = bdx00x00x00x00x00x00x08@x00x00x00x00x00x00x10@nv1 = Vector2d.frombytes(b)nprint(v1)nnnOUT:n(3.0, 4.0)nn

可散列的向量類

目前我們的向量類還是不可以散列的,

因此不能放入集合(set)內

hash(v1)nnnTypeError: unhashable type: Vector2dnn

下面我們得定義__hash__、__eq___方法,來使得向量類可散列。

(關於什麼是可散列,可以看第三章的內容)

當然,首先要做的是,讓向量不可變:

class Vector2d:n def __init__(self, x, y): # 將坐標轉換為浮點類型n # 使用兩個先導下劃線將變數標記為私有 【記住!只是標記,主要是同志開發人員,類似於常量使用大寫字母標註】n self.__x = float(x)n self.__y = float(y)nn @property # 用該裝飾器將讀值方法標記為特性n def x(self): # 讀值方法和公開屬性同名,都是xn return self.__xnn @propertyn def y(self):n return self.__ynn def __iter__(self):n # 讀取x、y的分量的時候,只讀取公開屬性,這樣來保持私有屬性的不變n return (i for i in (self.x, self.y))nn def __hash__(self):n # 為了保證散列值是固定不變的,我們首先得抱著所有屬性的變。 n # 根據__has__文檔的要求,我們通常使用異或運算符 ^ 來混合分量的散列值n return hash(self.x) ^ hash(self.y)nnn# testnv1 = Vector2d(3, 4)nset([v1])nv1.x = 1nnnOUT:nv1.x = 1nAttributeError: cant set attributenn

使用__ slots __ 類屬性節省內存空間

默認的情況下,Python在各個實例中名為 __ dict __ 的字典中存儲實例的屬性。由於底層使用了散列的特性來提示訪問的速度,字典會消耗大量的內存。通過slots屬性,讓解釋器再元組中記錄實例屬性,而不是用字典,這樣就能節省大量的內存了。

使用起來也十分方便,只需要定義我們需要的類屬性就行了

class Vector2d:n __slots__ = (__x,__y)n typecode = dn

完成這個屬性定義之後,

在同時處理上百萬個實例時,就能節省大量的內存。

作者寫了一個腳本生成 10 000 000 個向量實例:

使用dict來記錄屬性: RAM用量 1.5GB

定義__ solts __之後: RAM用量 655MB

當然,實際在使用這一特性的時候,還需要自己查閱相關文檔。

我這裡就不詳細說明了。

本章小結

在本章的例子中,目標是說明如何使用特殊方法和約定結構來定義一個行為良好、且符合Python風格的類

由於是做展示使用,我們也定義了v1 v2 v3 三個版本的類。

表面上是一個比一個更加完善。但實際上是否符合Python風格還需要實際考慮需求。

Tim Peter 在 Python之禪里說道:

簡介勝於複雜

符合Python風格的對象應該正好符合所需,二不是堆砌語言的特性。

一句話總結:

要構建符合Python風格的對象,那就要觀察真正的Python對象的行為。

每天的學習記錄都會 同步更新到:

微信公眾號: findyourownway

知乎專欄:zhuanlan.zhihu.com/zen-

Blog : www.ehcoblog.ml

Github: github.com/Ehco1996/


推薦閱讀:

《Django By Example》第一章 中文翻譯
[Python]因果檢驗工具
markdown for academia
[6] Python列表

TAG:Python |