摘要:一基本的序列協議首先,需要就維向量和二維向量的顯示模的計算等差異重新調整。假設維向量最多能處理維向量,訪問向量分量的代碼實現如下若傳入的參數在備選分量中可進行后續處理判斷分量的位置索引是否超出實例的邊界不支持非法的分量訪問,拋出。
導語:本文章記錄了本人在學習Python基礎之面向對象篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。
本文重點:
1、了解協議的概念以及利用__getitem__和__len__實現序列協議的方法;
2、掌握切片背后的__getitem__;
3、掌握動態訪問屬性背后的__getattr__和__setattr__;
4、掌握實現可散列對象背后精簡的__hash__和__eq__。
注:本文介紹的vector類將二維vector類推廣到多維,跟不上本文的朋友可以移步至《編寫符合Python風格的對象》先了解二維向量類的編寫。
一、基本的序列協議首先,需要就n維向量和二維向量的顯示、模的計算等差異重新調整。n維向量的設計包括初始化,迭代,輸出,向量實例轉為字節序列,求模,求布爾值,比較等內容,代碼如下:
import math import reprlib from array import array class Vector: typecode="d" def __init__(self,components): self._components=array(self.typecode,components) def __str__(self): return str(tuple(self)) def __iter__(self): return iter(self._components) def __repr__(self): classname=type(self).__name__ components=reprlib.repr(self._components) components=components[components.find("["):-1] return "{}({})".format(classname,components) def __eq__(self, other): return tuple(self)==tuple(other) def __abs__(self): return math.sqrt(sum(x*x for x in self)) def __bytes__(self): return (bytes(self.typecode,encoding="utf-8")+ bytes(array(self.typecode,self._components))) def __bool__(self): return bool(abs(self) @classmethod def frombytes(cls,seqs): typecode=chr(seqs[0]) memv=memoryview(seqs[1:]).cast(typecode) return cls(memv)
在Python中創建功能完善的序列類型無需使用繼承,只需要實現符合序列協議的__len__和__getitem__,具體代碼實現如下:
class Vector: #省略中間代碼 def __len__(self): return len(self._components) def __getitem__(self, item): return self._components[item]
在面向對象編程中,協議是非正式的接口,沒有強制力。因此如果知道類的具體使用場景,實現協議中的一部分也可以。例如,為了支持迭代只實現__getitem__方法即可。
二、切片原理 1、了解切片的行為在對序列切片(slice)的操作中,解釋器允許切片省略start,stop,stride中的部分值甚至是全部省略。通過dir(slice)查閱發現,是切片背后的indices在做這個工作。indices方法會整頓存儲數據屬性的元組,把start,stop,stride都變成非負數,而且都落在指定長度序列的邊界內。
例如slice(-3,None,None).indices(5)整頓完畢之后是(2,5,1)這樣合理的切片。
__getitem__是支持迭代取值的特殊方法。我們將上文的__getitem__改造成可以處理切片的方法,改造需要考慮到處理參數是否為合理切片,合理切片的操作結果是產生新的向量實例。
def __getitem__(self, index): cls=type(self) if isinstance(index,slice): return cls(self._components[index])#判斷參數為切片時返回新的向量實例 elif isinstance(index,numbers.Integral): return self._components[index]#判斷參數為數值時返回對應的數值 else: msg="{cls.__name__} indices must be integers" raise TypeError(msg.format(cls=cls))#判斷參數不合理時拋出TypeError三、動態存取屬性 1、訪問向量分量:__getattr__
n維向量沒有像二維向量一樣把訪問分量的方式直接在__init__中寫入,由于傳入的維數不確定無法采取窮舉分量的原始方法,為此我們需要借助__getattr__實現。假設n維向量最多能處理6維向量,訪問向量分量的代碼實現如下:
shortcut_names="xyztpq" def __getattr__(self, name): cls=type(self) if len(name)==1: index=cls.shortcut_names.find(str(name))#若傳入的參數在備選分量中可進行后續處理 if 0<=indexTips:代碼嚴謹之處在于傳入的參數即使在備選分量之中,也有可能會超出實例的邊界,因此涉及到索引和邊界需要認真注意這一點。
2、保持行為一致:__setattr__盡管我們實現了__getattr__,但事實上目前的n維向量存在行為不一致的問題,先看一段代碼:
v=Vector(range(5)) print(v.y)#輸出1.0 v.y=6 print(v.y)#輸出6 print(v)#輸出(0.0, 1.0, 2.0, 3.0, 4.0)上面的例子顯示我們可以訪問6維向量的y分量,但是問題在于我們為y分量賦值的改動沒有影響到向量實例v。這種行為是不一致的,并且還沒有拋出錯誤令人匪夷所思。本文中我們希望向量分量是只讀不可變的,也就是說我們要對修改向量分量這種不當的行為拋出Error。因此需要額外構造__setattr__,代碼實現如下:
def __setattr__(self, key, value): cls=type(self) if len(key)==1: if key in self.shortcut_names: error="can"t set value to attribute {attr_name!r}" elif key.islower(): error="can"t set attributes "a" to "z" in {cls_name!r}" else: error="" if error:#寫嵌套語句的時候要始終把握住邏輯思路。 msg = error.format(cls_name=cls.__name__, attr_name=key) raise AttributeError(msg) super().__setattr__(key,value)#在超類上調用__setattr__方法來提供標準行為。小結:如果定義了__getattr__方法,那么也要定義__setattr__方法,這樣才能避免行為不一致。
四、可散列的對象可散列對象應滿足的三個條件在此不再贅述,對于n維向量類而言需要做兩件事將其散列化:
1、利用異或運算符構造__hash__構造思路是將hash()應用到向量中的每個元素,并用異或運算符進行聚合計算。由于處理的向量維數提高,采用歸約函數functools.reduce處理。
import operator from functools import reduce def __hash__(self): hashes=map(hash,self._components) return reduce(operator.xor,hashes)2、通過zip優化n維向量的比較方法__eq__上文初始給出的比較方法是粗糙的,下面針對兩個維數均不確定的向量進行比較,代碼如下:
def __eq__(self, other): if len(self)!=len(other):#數組數量的比較很關鍵 return False for x,y in zip(self,other): if x!=y: return False return True數組數量的比較時很關鍵的,因為zip在比較數量不等的序列時會隨著一個輸入的耗盡而停止迭代,并且不拋出Error。
回到正題,上述的邏輯關系可以進一步精簡。通過all函數可以把for循環替代:def __eq__(self, other): return len(self)==len(other) and all(x==y for x,y in zip(self,other))本人更喜歡后者這種簡潔且準確的代碼書寫方式。
五、格式化顯示理解n維向量的超球面坐標(r,θ1,θ2,θ3,...,θn-1)計算公式需要額外的數學基礎,此處的格式化輸出在本質上與《編寫符合Python風格的對象》中的格式化輸出并無明顯區別,此處不作詳述,感興趣的朋友可以查看如下的代碼:
def angle(self, n): #使用公式計算角坐標 r = math.sqrt(sum(x * x for x in self[n:])) a = math.atan2(r, self[n-1]) if (n == len(self) - 1) and (self[-1] < 0): return math.pi * 2 - a else: return a def angles(self): return (self.angle(n) for n in range(1, len(self)))#計算所有角坐標并存入生成器表達式中 def __format__(self, fmt_spec=""): if fmt_spec.endswith("h"): # 超球面坐標標識符 fmt_spec = fmt_spec[:-1] coords = itertools.chain([abs(self)],self.angles()) #利用itertools.chain無縫迭代模和角坐標 outer_fmt = "<{}>" else: coords = self outer_fmt = "({})" components = (format(c, fmt_spec) for c in coords) #格式化極坐標的各元素并存入生成器中 return outer_fmt.format(", ".join(components))
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/41277.html
摘要:具體方法和上一篇一樣,也是用各個分量的哈希值進行異或運算,由于的分量可能很多,這里我們使用函數來歸約異或值。每個分量被映射成了它們的哈希值,這些哈希值再歸約成一個值這里的傳入了第三個參數,并且建議最好傳入第三個參數。 《流暢的Python》筆記。本篇是面向對象慣用方法的第三篇。本篇將以上一篇中的Vector2d為基礎,定義多維向量Vector。 1. 前言 自定義Vector類的行為...
摘要:例如,的序列協議只需要和兩個方法。任何類如,只要使用標準的簽名和語義實現了這兩個方法,就能用在任何期待序列的地方。方法開放了內置序列實現的棘手邏輯,用于優雅地處理缺失索引和負數索引,以及長度超過目標序列的切片。 序列的修改、散列和切片 接著造Vector2d類 要達到的要求 為了編寫Vector(3, 4) 和 Vector(3, 4, 5) 這樣的代碼,我們可以讓 init 法接受任...
摘要:導語本文章匯總了本人在學習基礎之緒論篇數據結構篇函數篇面向對象篇控制流程篇和元編程篇學習筆記的鏈接,打算入門的朋友們可以按需查看并交流。 導語:本文章匯總了本人在學習Python基礎之緒論篇、數據結構篇、函數篇、面向對象篇、控制流程篇和元編程篇學習筆記的鏈接,打算入門Python的朋友們可以按需查看并交流。 第一部分:緒論篇 1、Python數據模型 第二部分:數據結構篇 2、序列構成...
閱讀 3569·2021-11-22 15:11
閱讀 4660·2021-11-18 13:15
閱讀 2714·2019-08-29 14:08
閱讀 3589·2019-08-26 13:49
閱讀 3106·2019-08-26 12:17
閱讀 3298·2019-08-26 11:54
閱讀 3123·2019-08-26 10:58
閱讀 2043·2019-08-26 10:21