摘要:例如,的序列協議只需要和兩個方法。任何類如,只要使用標準的簽名和語義實現了這兩個方法,就能用在任何期待序列的地方。方法開放了內置序列實現的棘手邏輯,用于優雅地處理缺失索引和負數索引,以及長度超過目標序列的切片。
序列的修改、散列和切片 接著造Vector2d類
為了編寫Vector(3, 4) 和 Vector(3, 4, 5) 這樣的代碼,我們可以讓 init 法接受任意個參數(通過 *args)
如果 Vector 實例的分量超過 6 個,repr() 生成的字符串就會使用 ... 省略一部
分,使用 reprlib 模塊可以生成長度有限的表示形式
from array import array import reprlib import math class Vector: typecode = "d" def __init__(self, components): self._components = array(self.typecode, components) def __iter__(self): return iter(self._components) # 這里是重點 def __repr__(self): components = reprlib.repr(self._components) components = components[components.find("["):-1] return "Vector({})".format(components) print(Vector([3.1, 4.2])) print(Vector((3, 4, 5))) print(Vector(range(10)))
? 使用 reprlib.repr() 函數獲取 self._components 的有限長度表示形式(如
array("d", [0.0, 1.0, 2.0, 3.0, 4.0, ...]))。
? 把字符串插入 Vector 的構造方法調用之前,去掉前面的 array("d" 和后面的 )。
在面向對象編程中,
協議是非正式的接口,只在文檔中定義,在代碼中不定義。
例如,Python 的序列協議只需要 len 和 getitem 兩個方法。
任何類(如 Spam),只要使用標準的簽名和語義實現了這兩個方法,就能用在任何期待序列的地方。
第一章的代碼再次給出import collections Card = collections.namedtuple("Card", ["rank", "suit"]) class FrenchDeck: ranks = [str(n) for n in range(2, 11)] + list("JQKA") suits = "spades diamonds clubs hearts".split() def __init__(self): self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] def __len__(self): return len(self._cards) def __getitem__(self, position): return self._cards[position]Vector類第2版:可切片的序列
from array import array import reprlib import math class Vector(object): typecode = "d" def __init__(self, components): self._components = array(self.typecode, components) def __iter__(self): return iter(self._components) def __repr__(self): components = reprlib.repr(self._components) components = components[components.find("["):-1] return "Vector({})".format(components) def __str__(self): return str(tuple(self)) def __bytes__(self): return (bytes([ord(self.typecode)]) + bytes(self._components)) def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): return math.sqrt(sum(x * x for x in self)) def __bool__(self): return bool(abs(self)) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) def __len__(self): return len(self._components) def __getitem__(self, index): return self._components[index] v1 = Vector([3, 4, 5]) print(len(v1)) print(v1[0], v1[-1]) v7 = Vector(range(7)) print(v7[1:4])
現在連切片都支持了,不過尚不完美。如果 Vector 實例的切片也是 Vector
實例,而不是數組,那就更好了。
把 Vector 實例的切片也變成 Vector 實例,我們不能簡單地委托給數組切片。我們
要分析傳給 getitem 方法的參數,做適當的處理。
class MySeq: def __getitem__(self, index): return index s = MySeq() print(s[1]) print(s[1:4]) print(s[1:4:2]) print(s[1:4:2, 9]) print(s[1:4:2, 7:9])
? 1:4 表示法變成了 slice(1, 4, None)。
? slice(1, 4, 2) 的意思是從 1 開始,到 4 結束,步幅為 2。
? 神奇的事發生了:如果 [] 中有逗號,那么 getitem 收到的是元組。
? 元組中甚至可以有多個切片對象。
print(slice) print(dir(slice))
["__class__", "__delattr__", "__dir__", "__doc__", "__eq__", "__format__", "__ge__", "__getattribute__", "__gt__", "__hash__", "__init__", "__init_subclass__", "__le__", "__lt__", "__ne__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__", "indices", "start", "step", "stop"]
通過審查 slice,發現它有 start、stop 和 step 數據屬性,以及 indices 方法。
indices 方法開放了內置序列實現的棘手邏輯,用于優雅地處理缺失索引和
負數索引,以及長度超過目標序列的切片。
這個方法會“整頓”元組,把 start、stop 和
stride 都變成非負數,而且都落在指定長度序列的邊界內。一句話 把負數索引和超出長度的索引調整成 正常的索引
aa = "ABCDE" print(slice(None, 10, 2).indices(5)) print(slice(-3, None, None).indices(5)) print("="*40) print(slice(None, 10, 2).indices(len(aa))) print(slice(-3, None, None).indices(len(aa))) print(aa[-3:])能處理切片的__getitem__方法
from array import array import reprlib import math import numbers class Vector(object): typecode = "d" def __init__(self, components): self._components = array(self.typecode, components) def __iter__(self): return iter(self._components) def __repr__(self): components = reprlib.repr(self._components) components = components[components.find("["):-1] return "Vector({})".format(components) def __len__(self): return len(self._components) ##[1:4] 返回一個向量對象 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)) v7 = Vector(range(7)) print(v7[-1]) print(v7[1:4]) print(v7[-1:])Vector類第3版:動態存取屬性
我們可以在 Vector 中編寫四個特性,但這樣太麻煩。
特殊方法 getattr 提供了更好的方式。
屬性查找失敗后,解釋器會調用 getattr 方法。
簡單來說,對 my_obj.x 表達式,
Python 會檢查 my_obj 實例有沒有名為 x 的屬性;
如果沒有,到類(my_obj.__class__)中查找;
如果還沒有,順著繼承樹繼續查找。
如果依舊找不到,調用 my_obj 所屬類中定義的 getattr 方法,傳入 self 和屬性名稱的字符串形式(如 "x")。
from array import array import reprlib import math import numbers class Vector(object): typecode = "d" def __init__(self, components): self._components = array(self.typecode, components) def __iter__(self): return iter(self._components) def __repr__(self): components = reprlib.repr(self._components) components = components[components.find("["):-1] return "Vector({})".format(components) shortcut_names = "xyzt" def __getattr__(self, name): cls = type(self) if len(name) == 1: pos = cls.shortcut_names.find(name) if 0 <= pos < len(self._components): return self._components[pos] msg = "{.__name__!r} object has no attribute {!r}" raise AttributeError(msg.format(cls, name)) def __setattr__(self, name, value): cls = type(self) if len(name) == 1: # 如果 name 是 xyzt 中的一個,設置特殊的錯誤消息。 if name in cls.shortcut_names: error = "readonly attribute {attr_name!r}" # 如果 name 是小寫字母,為所有小寫字母設置一個錯誤消息。 elif name.islower(): error = "can"t set attributes "a" to "z" in {cls_name!r}" #否則,把錯誤消息設為空字符串。 else: error = "" #如果有錯誤消息,拋出AttributeError。 if error: msg = error.format(cls_name=cls.__name__, attr_name=name) raise AttributeError(msg) # 默認情況:在超類上調用 __setattr__ 方法,提供標準行為。 super().__setattr__(name, value) v = Vector(range(5)) print(v) # 這個設置法 沒用 v.p = 10 print(v.x) print(v)
super() 函數用于動態訪問超類的方法,對 Python 這樣支持多重繼承的動態
語言來說,必須能這么做。程序員經常使用這個函數把子類方法的某些任務委托給超
類中適當的方法
注意,我們沒有禁止為全部屬性賦值,只是禁止為單個小寫字母屬性賦值,以防與只讀屬
性 x、y、z 和 t 混淆。
functools.reduce() 可以替換成 sum()
這里的原理它的關鍵思想是,把一系列值歸約成單個值。
reduce() 函數的第一個參數是接受兩個參數的函數,第二個參數是一個可迭代的對象。 假如有個接受兩個參數的 fn 函數和一個 lst
列表。
調用 reduce(fn, lst) 時,fn 會應用到第一對元素上,即 fn(lst[0],lst[1]),生成第一個結果r1。然后,fn 會應用到 r1 和下一個元素上,即 fn(r1,lst[2]),生成第二個結果 r2。
接著,調用 fn(r2, lst[3]),生成 r3……直到最后一個元素,返回最后得到的結果 rN。
如:
>>> import functools >>> functools.reduce(lambda a,b: a*b, range(1, 6)) 120reduce接著用
import functools aa = functools.reduce(lambda a, b: a ^ b, range(1,6)) print(aa) # operator--操作符函數 # https://blog.csdn.net/shengmingqijiquan/article/details/53005129 import operator bb = functools.reduce(operator.xor, range(6)) print(bb)使用我喜歡的方式編寫 Vector.__hash__ 方法,我們要導入 functools 和
operator 模塊。(任性的作者)
import functools # ? import operator # ? class Vector: typecode = "d" # 排版需要,省略了很多行... def __eq__(self, other): # ? return tuple(self) == tuple(other) def __hash__(self): hashes = (hash(x) for x in self._components) # ? return functools.reduce(operator.xor, hashes, 0) # ? # 排版需要,省略了很多行...
? 創建一個生成器表達式,惰性計算各個分量的散列值。
? 把 hashes 提供給 reduce 函數,使用 xor 函數計算聚合的散列值;第三個參數,0 是
初始值(參見下面的警告框)。
def __eq__(self, other): if len(self) != len(other): # ? return False for a, b in zip(self, other): # ? if a != b: # ? return False return True # ?
? zip 函數生成一個由元組構成的生成器,元組中的元素來自參數傳入的各個可迭代對
象。如果不熟悉 zip 函數,請閱讀“出色的 zip 函數”附注欄。前面比較長度的測試是有
必要的,因為一旦有一個輸入耗盡,zip 函數會立即停止生成值,而且不發出警告。
使用 zip 和 all 函數實現 Vector.__eq__ 方法
def __eq__(self, other): return len(self) == len(other) and all(a == b for a, b in zip(self, other))zip 內置函數的使用示例
>>> zip(range(3), "ABC") # ?>>> list(zip(range(3), "ABC")) # ? [(0, "A"), (1, "B"), (2, "C")] >>> list(zip(range(3), "ABC", [0.0, 1.1, 2.2, 3.3])) # ? [(0, "A", 0.0), (1, "B", 1.1), (2, "C", 2.2)] >>> from itertools import zip_longest # ? >>> list(zip_longest(range(3), "ABC", [0.0, 1.1, 2.2, 3.3], fillvalue=-1)) [(0, "A", 0.0), (1, "B", 1.1), (2, "C", 2.2), (-1, -1, 3.3)]
? zip 有個奇怪的特性:當一個可迭代對象耗盡后,它不發出警告就停止。
? itertools.zip_longest 函數的行為有所不同:使用可選的 fillvalue(默認
值為 None)填充缺失的值,因此可以繼續產出,直到最長的可迭代對象耗盡。
__format__提供格式化方法,詳情和具體代碼 page 348
小總結repr 如果信息展示過長. 用reprlib 模塊可以縮短
2.切片原理
slice(None, 10, 2).indices(5) 負責轉換成可用的索引
len 和 _getitem 實現切片的重要方法
屬性查找失敗后,解釋器會調用 getattr 方法。利用這個特性,可以搞一些事情
4.reduce 的使用方法
5.zip函數 簡單理解矩陣對應
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/41752.html
摘要:具體方法和上一篇一樣,也是用各個分量的哈希值進行異或運算,由于的分量可能很多,這里我們使用函數來歸約異或值。每個分量被映射成了它們的哈希值,這些哈希值再歸約成一個值這里的傳入了第三個參數,并且建議最好傳入第三個參數。 《流暢的Python》筆記。本篇是面向對象慣用方法的第三篇。本篇將以上一篇中的Vector2d為基礎,定義多維向量Vector。 1. 前言 自定義Vector類的行為...
摘要:一基本的序列協議首先,需要就維向量和二維向量的顯示模的計算等差異重新調整。假設維向量最多能處理維向量,訪問向量分量的代碼實現如下若傳入的參數在備選分量中可進行后續處理判斷分量的位置索引是否超出實例的邊界不支持非法的分量訪問,拋出。 導語:本文章記錄了本人在學習Python基礎之面向對象篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。 本文重點: 1、了解協議的...
摘要:把具名元組以的形式返回,我們可以利用它來把元組里的信息友好地呈現出來。數組支持所有跟可變序列有關的操作,包括和。雙向隊列和其他形式的隊列類雙向隊列是一個線程安全可以快速從兩端添加或者刪除元素的數據類型。 列表表達式 >>> symbols = $¢£¥€¤ >>> codes = [ord(symbol) for symbol in symbols] >>> codes [36, 16...
摘要:繼承的優缺點推出繼承的初衷是讓新手順利使用只有專家才能設計出來的框架。多重繼承的真實應用多重繼承能發揮積極作用。即便是單繼承,這個原則也能提升靈活性,因為子類化是一種緊耦合,而且較高的繼承樹容易倒。 繼承的優缺點 推出繼承的初衷是讓新手順利使用只有專家才能設計出來的框架。——Alan Kay 子類化內置類型很麻煩 (如 list 或 dict)) ,別搞這種 直接子類化內置類型(如 ...
閱讀 2731·2021-11-24 09:39
閱讀 1646·2021-09-28 09:35
閱讀 1119·2021-09-06 15:02
閱讀 1306·2021-07-25 21:37
閱讀 2726·2019-08-30 15:53
閱讀 3643·2019-08-30 14:07
閱讀 713·2019-08-30 11:07
閱讀 3511·2019-08-29 18:36