摘要:例如,的序列協(xié)議只需要和兩個(gè)方法。任何類如,只要使用標(biāo)準(zhǔn)的簽名和語義實(shí)現(xiàn)了這兩個(gè)方法,就能用在任何期待序列的地方。方法開放了內(nèi)置序列實(shí)現(xiàn)的棘手邏輯,用于優(yōu)雅地處理缺失索引和負(fù)數(shù)索引,以及長(zhǎng)度超過目標(biāo)序列的切片。
序列的修改、散列和切片 接著造Vector2d類
為了編寫Vector(3, 4) 和 Vector(3, 4, 5) 這樣的代碼,我們可以讓 init 法接受任意個(gè)參數(shù)(通過 *args)
如果 Vector 實(shí)例的分量超過 6 個(gè),repr() 生成的字符串就會(huì)使用 ... 省略一部
分,使用 reprlib 模塊可以生成長(zhǎng)度有限的表示形式
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) # 這里是重點(diǎn) 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() 函數(shù)獲取 self._components 的有限長(zhǎng)度表示形式(如
array("d", [0.0, 1.0, 2.0, 3.0, 4.0, ...]))。
? 把字符串插入 Vector 的構(gòu)造方法調(diào)用之前,去掉前面的 array("d" 和后面的 )。
在面向?qū)ο缶幊讨校?/p>
協(xié)議是非正式的接口,只在文檔中定義,在代碼中不定義。
例如,Python 的序列協(xié)議只需要 len 和 getitem 兩個(gè)方法。
任何類(如 Spam),只要使用標(biāo)準(zhǔn)的簽名和語義實(shí)現(xiàn)了這兩個(gè)方法,就能用在任何期待序列的地方。
第一章的代碼再次給出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])
現(xiàn)在連切片都支持了,不過尚不完美。如果 Vector 實(shí)例的切片也是 Vector
實(shí)例,而不是數(shù)組,那就更好了。
把 Vector 實(shí)例的切片也變成 Vector 實(shí)例,我們不能簡(jiǎn)單地委托給數(shù)組切片。我們
要分析傳給 getitem 方法的參數(shù),做適當(dāng)?shù)奶幚怼?/p>
切片原理
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 結(jié)束,步幅為 2。
? 神奇的事發(fā)生了:如果 [] 中有逗號(hào),那么 getitem 收到的是元組。
? 元組中甚至可以有多個(gè)切片對(duì)象。
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,發(fā)現(xiàn)它有 start、stop 和 step 數(shù)據(jù)屬性,以及 indices 方法。
indices 方法開放了內(nèi)置序列實(shí)現(xiàn)的棘手邏輯,用于優(yōu)雅地處理缺失索引和
負(fù)數(shù)索引,以及長(zhǎng)度超過目標(biāo)序列的切片。
這個(gè)方法會(huì)“整頓”元組,把 start、stop 和
stride 都變成非負(fù)數(shù),而且都落在指定長(zhǎng)度序列的邊界內(nèi)。一句話 把負(fù)數(shù)索引和超出長(zhǎng)度的索引調(diào)整成 正常的索引
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] 返回一個(gè)向量對(duì)象 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版:動(dòng)態(tài)存取屬性
我們可以在 Vector 中編寫四個(gè)特性,但這樣太麻煩。
特殊方法 getattr 提供了更好的方式。
屬性查找失敗后,解釋器會(huì)調(diào)用 getattr 方法。
簡(jiǎn)單來說,對(duì) my_obj.x 表達(dá)式,
Python 會(huì)檢查 my_obj 實(shí)例有沒有名為 x 的屬性;
如果沒有,到類(my_obj.__class__)中查找;
如果還沒有,順著繼承樹繼續(xù)查找。
如果依舊找不到,調(diào)用 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 中的一個(gè),設(shè)置特殊的錯(cuò)誤消息。 if name in cls.shortcut_names: error = "readonly attribute {attr_name!r}" # 如果 name 是小寫字母,為所有小寫字母設(shè)置一個(gè)錯(cuò)誤消息。 elif name.islower(): error = "can"t set attributes "a" to "z" in {cls_name!r}" #否則,把錯(cuò)誤消息設(shè)為空字符串。 else: error = "" #如果有錯(cuò)誤消息,拋出AttributeError。 if error: msg = error.format(cls_name=cls.__name__, attr_name=name) raise AttributeError(msg) # 默認(rèn)情況:在超類上調(diào)用 __setattr__ 方法,提供標(biāo)準(zhǔn)行為。 super().__setattr__(name, value) v = Vector(range(5)) print(v) # 這個(gè)設(shè)置法 沒用 v.p = 10 print(v.x) print(v)
super() 函數(shù)用于動(dòng)態(tài)訪問超類的方法,對(duì) Python 這樣支持多重繼承的動(dòng)態(tài)
語言來說,必須能這么做。程序員經(jīng)常使用這個(gè)函數(shù)把子類方法的某些任務(wù)委托給超
類中適當(dāng)?shù)姆椒?/p>
注意,我們沒有禁止為全部屬性賦值,只是禁止為單個(gè)小寫字母屬性賦值,以防與只讀屬
性 x、y、z 和 t 混淆。
functools.reduce() 可以替換成 sum()
這里的原理它的關(guān)鍵思想是,把一系列值歸約成單個(gè)值。
reduce() 函數(shù)的第一個(gè)參數(shù)是接受兩個(gè)參數(shù)的函數(shù),第二個(gè)參數(shù)是一個(gè)可迭代的對(duì)象。 假如有個(gè)接受兩個(gè)參數(shù)的 fn 函數(shù)和一個(gè) lst
列表。
調(diào)用 reduce(fn, lst) 時(shí),fn 會(huì)應(yīng)用到第一對(duì)元素上,即 fn(lst[0],lst[1]),生成第一個(gè)結(jié)果r1。然后,fn 會(huì)應(yīng)用到 r1 和下一個(gè)元素上,即 fn(r1,lst[2]),生成第二個(gè)結(jié)果 r2。
接著,調(diào)用 fn(r2, lst[3]),生成 r3……直到最后一個(gè)元素,返回最后得到的結(jié)果 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--操作符函數(shù) # https://blog.csdn.net/shengmingqijiquan/article/details/53005129 import operator bb = functools.reduce(operator.xor, range(6)) print(bb)使用我喜歡的方式編寫 Vector.__hash__ 方法,我們要導(dǎo)入 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) # ? # 排版需要,省略了很多行...
? 創(chuàng)建一個(gè)生成器表達(dá)式,惰性計(jì)算各個(gè)分量的散列值。
? 把 hashes 提供給 reduce 函數(shù),使用 xor 函數(shù)計(jì)算聚合的散列值;第三個(gè)參數(shù),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 函數(shù)生成一個(gè)由元組構(gòu)成的生成器,元組中的元素來自參數(shù)傳入的各個(gè)可迭代對(duì)
象。如果不熟悉 zip 函數(shù),請(qǐng)閱讀“出色的 zip 函數(shù)”附注欄。前面比較長(zhǎng)度的測(cè)試是有
必要的,因?yàn)橐坏┯幸粋€(gè)輸入耗盡,zip 函數(shù)會(huì)立即停止生成值,而且不發(fā)出警告。
使用 zip 和 all 函數(shù)實(shí)現(xiàn) Vector.__eq__ 方法
def __eq__(self, other): return len(self) == len(other) and all(a == b for a, b in zip(self, other))zip 內(nèi)置函數(shù)的使用示例
>>> 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 有個(gè)奇怪的特性:當(dāng)一個(gè)可迭代對(duì)象耗盡后,它不發(fā)出警告就停止。
? itertools.zip_longest 函數(shù)的行為有所不同:使用可選的 fillvalue(默認(rèn)
值為 None)填充缺失的值,因此可以繼續(xù)產(chǎn)出,直到最長(zhǎng)的可迭代對(duì)象耗盡。
__format__提供格式化方法,詳情和具體代碼 page 348
小總結(jié)repr 如果信息展示過長(zhǎng). 用reprlib 模塊可以縮短
2.切片原理
slice(None, 10, 2).indices(5) 負(fù)責(zé)轉(zhuǎn)換成可用的索引
len 和 _getitem 實(shí)現(xiàn)切片的重要方法
屬性查找失敗后,解釋器會(huì)調(diào)用 getattr 方法。利用這個(gè)特性,可以搞一些事情
4.reduce 的使用方法
5.zip函數(shù) 簡(jiǎn)單理解矩陣對(duì)應(yīng)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/41752.html
摘要:具體方法和上一篇一樣,也是用各個(gè)分量的哈希值進(jìn)行異或運(yùn)算,由于的分量可能很多,這里我們使用函數(shù)來歸約異或值。每個(gè)分量被映射成了它們的哈希值,這些哈希值再歸約成一個(gè)值這里的傳入了第三個(gè)參數(shù),并且建議最好傳入第三個(gè)參數(shù)。 《流暢的Python》筆記。本篇是面向?qū)ο髴T用方法的第三篇。本篇將以上一篇中的Vector2d為基礎(chǔ),定義多維向量Vector。 1. 前言 自定義Vector類的行為...
摘要:一基本的序列協(xié)議首先,需要就維向量和二維向量的顯示模的計(jì)算等差異重新調(diào)整。假設(shè)維向量最多能處理維向量,訪問向量分量的代碼實(shí)現(xiàn)如下若傳入的參數(shù)在備選分量中可進(jìn)行后續(xù)處理判斷分量的位置索引是否超出實(shí)例的邊界不支持非法的分量訪問,拋出。 導(dǎo)語:本文章記錄了本人在學(xué)習(xí)Python基礎(chǔ)之面向?qū)ο笃闹攸c(diǎn)知識(shí)及個(gè)人心得,打算入門Python的朋友們可以來一起學(xué)習(xí)并交流。 本文重點(diǎn): 1、了解協(xié)議的...
摘要:把具名元組以的形式返回,我們可以利用它來把元組里的信息友好地呈現(xiàn)出來。數(shù)組支持所有跟可變序列有關(guān)的操作,包括和。雙向隊(duì)列和其他形式的隊(duì)列類雙向隊(duì)列是一個(gè)線程安全可以快速從兩端添加或者刪除元素的數(shù)據(jù)類型。 列表表達(dá)式 >>> symbols = $¢£¥€¤ >>> codes = [ord(symbol) for symbol in symbols] >>> codes [36, 16...
摘要:繼承的優(yōu)缺點(diǎn)推出繼承的初衷是讓新手順利使用只有專家才能設(shè)計(jì)出來的框架。多重繼承的真實(shí)應(yīng)用多重繼承能發(fā)揮積極作用。即便是單繼承,這個(gè)原則也能提升靈活性,因?yàn)樽宇惢且环N緊耦合,而且較高的繼承樹容易倒。 繼承的優(yōu)缺點(diǎn) 推出繼承的初衷是讓新手順利使用只有專家才能設(shè)計(jì)出來的框架。——Alan Kay 子類化內(nèi)置類型很麻煩 (如 list 或 dict)) ,別搞這種 直接子類化內(nèi)置類型(如 ...
閱讀 2817·2021-11-24 09:39
閱讀 1672·2021-09-28 09:35
閱讀 1148·2021-09-06 15:02
閱讀 1365·2021-07-25 21:37
閱讀 2797·2019-08-30 15:53
閱讀 3675·2019-08-30 14:07
閱讀 735·2019-08-30 11:07
閱讀 3553·2019-08-29 18:36