成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Python學(xué)習(xí)之路29-序列的修改、散列和切片

馬忠志 / 2208人閱讀

摘要:具體方法和上一篇一樣,也是用各個(gè)分量的哈希值進(jìn)行異或運(yùn)算,由于的分量可能很多,這里我們使用函數(shù)來歸約異或值。每個(gè)分量被映射成了它們的哈希值,這些哈希值再歸約成一個(gè)值這里的傳入了第三個(gè)參數(shù),并且建議最好傳入第三個(gè)參數(shù)。

《流暢的Python》筆記。

本篇是“面向?qū)ο髴T用方法”的第三篇。本篇將以上一篇中的Vector2d為基礎(chǔ),定義多維向量Vector。

1. 前言

自定義Vector類的行為將與Python標(biāo)準(zhǔn)中的不可變扁平序列一樣,它將支持如下功能:

基本的序列協(xié)議:__len____getitem__;

正確表述擁有很多元素的實(shí)例;

適當(dāng)?shù)那衅С郑糜谏尚碌?b>Vector實(shí)例;

綜合各個(gè)元素的值計(jì)算散列值;

自定義的格式語言擴(kuò)展。

本篇還將通過__getattr__方法實(shí)現(xiàn)屬性的動(dòng)態(tài)存?。m然序列類型通常不會(huì)這么做),以及穿插討論一個(gè)概念:把協(xié)議當(dāng)做正式接口。我們將說明協(xié)議和鴨子類型之間的關(guān)系,以及對(duì)自定義類型的影響。

2. 初版Vector

Vector的構(gòu)造方法將和所有內(nèi)置序列類型一樣,以可迭代對(duì)象為參數(shù)。如果其中元素過多,repr()函數(shù)返回的字符串將會(huì)使用...省略一部分內(nèi)容,它的初始版本如下:

# 代碼1
from array import array
import reprlib
import math

class Vector:
    typecode = "d"

    def __init__(self, components):  # 以可迭代對(duì)象為參數(shù)
        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):   # 和Vector2d相同
        return str(tuple(self))

    def __bytes__(self):
        return (bytes([ord(self.typecode)]) + bytes(self._components))

    def __eq__(self, other):   # 和Vector2d相同
        return tuple(self) == tuple(other)

    def __abs__(self):
        return math.sqrt(sum(x * x for x in self))

    def __bool__(self):   # 和Vector2d相同
        return bool(abs(self))

    @classmethod
    def frombytes(cls, octets):
        typecode = chr(octets[0])
        memv = memoryview(octets[1:]).cast(typecode)
        return cls(memv)   # 去掉了Vector2d中的星號(hào)*

之所以沒有直接繼承制Vector2d,既是因?yàn)檫@兩個(gè)類的構(gòu)造方法不兼容,也是因?yàn)槲覀円獮?b>Vector實(shí)現(xiàn)序列協(xié)議。

3. 協(xié)議和鴨子類型

協(xié)議和鴨子類型在之前的文章中也有所提及。在面向?qū)ο缶幊讨校?strong>協(xié)議是非正式的接口,只在文檔中定義,在代碼中不定義。

在Python中,只要實(shí)現(xiàn)了協(xié)議需要的某些方法,其實(shí)就算實(shí)現(xiàn)了協(xié)議,而不一定需要繼承。比如只要實(shí)現(xiàn)了__len____getitem__這兩個(gè)方法,那么這個(gè)類就是滿足序列協(xié)議的,而不需要從什么“序列基類”繼承。

鴨子類型:和現(xiàn)實(shí)中相反,Python中確定一個(gè)東西是不是“鴨子”,不是測它的“DNA”是不是”鴨子“的DNA,而是看這東西像不像只鴨子。只要像”鴨子“,那它就是“鴨子”。比如,只要一個(gè)類實(shí)現(xiàn)了__len____getitem__方法,那它就是序列類,而不必管它是從哪來的;文件類對(duì)象也常是鴨子類型。

4. 第2版Vector:支持切片

Vector變?yōu)樾蛄蓄愋?,并能正確返回切片:

# 代碼2,將以下代碼添加到初版Vector中
class Vector:
    -- snip --
    def __len__(self):
        return len(self._components)
    
    def __getitem__(self, index):
        cls = type(self)
        if isinstance(index, slice):  # 如果index是個(gè)切片類型,則構(gòu)造新實(shí)例
            return cls(self._components[index])
        elif isinstance(index, numbers.Integral):  # 如果index是個(gè)數(shù),則直接返回
            return self._components[index]
        else:
            msg = "{cls.__name__} indices must be integers"
            raise TypeError(msg.format(cls=cls))

如果__getitem__函數(shù)直接返回切片:return self._components[index],那么得到的數(shù)據(jù)將是array類型,而不是Vector類型。正是為了使切片的類型正確,這里才做了類型判斷。

上述代碼中用到了slice類型,它是Python的內(nèi)置類型,這里順便補(bǔ)充一下切片原理,直接上代碼:

# 代碼3
>>> class MySeq:
...     def __getitem__(self, index):
...         return index  # 直接返回傳給它的值
...    
>>> s = MySeq()
>>> s[1]   
1  # 單索引,沒啥新奇的
>>> s[1:3]
slice(1, 3, None)  # 返回來一個(gè)slice類型
>>> s[1:10:2]
slice(1, 10, 2)    # 注意slice類型的結(jié)構(gòu)
>>> s[1:10:2, 9]
(slice(1, 10, 2), 9)   # 如果[]中有逗號(hào),__getitem__收到的是元組
>>> s[1:10:2, 7:9]
(slice(1, 10, 2), slice(7, 9, None))

>>> dir(slice)  # 注意最后四個(gè)元素
["__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"]

當(dāng)我們用dir()函數(shù)獲取slice的屬性時(shí),發(fā)現(xiàn)它有start,stopstep數(shù)據(jù)屬性,并且還有一個(gè)indices方法,這里重點(diǎn)說說這個(gè)indices方法。它接收一個(gè)長度參數(shù)len,并根據(jù)這個(gè)lenslice類型的start,stopstep三個(gè)參數(shù)正確轉(zhuǎn)換成在長度范圍內(nèi)的非負(fù)數(shù),具體用法如下:

# 代碼4
>>> slice(None, 10, 2).indices(5)
(0, 5, 2)  # 將這些煩人的索引統(tǒng)統(tǒng)轉(zhuǎn)換成明確的正向索引
>>> slice(-3, None, None).indices(5)
(2, 5, 1)

自定義Vector類中并沒有使用這個(gè)方法,因?yàn)?b>Vector的底層我們使用了array.array數(shù)據(jù)類型,切片的具體操作不用我們自行編寫。但如果你的類沒有這樣的底層序列類型做支撐,那么slice.indices方法將為你節(jié)省大量時(shí)間。

5. 第3版Vector:動(dòng)態(tài)存儲(chǔ)屬性

目前版本的Vector中,沒有辦法通過名稱訪問向量的分量(如v.xv.y),而且現(xiàn)在的Vector可能存在大量分量。不過,如果能通過單個(gè)字母訪問前幾個(gè)分量的話,這樣將很方便,也更人性化?,F(xiàn)在,我們想用x,y,z,t四個(gè)字母分別代替v[0],v[1],v[2]v[3],但具體做法并不是為實(shí)例添加這四個(gè)屬性,并且我們也不想在運(yùn)行時(shí)實(shí)例能動(dòng)態(tài)添加單個(gè)字母的屬性,更不想實(shí)例能通過這四個(gè)字母修改Vectorself._components的值。換句話說,我們只想通過這四個(gè)字母提供一種較為方便的訪問方式,僅此而已。而要實(shí)現(xiàn)這樣的功能,則需要實(shí)現(xiàn)__getattr____setattr__方法,以下是它們的代碼:

# 代碼5.1
class Vector:
    -- snip --
    
    shortcut_name = "xyzt"

    def __getattr__(self, name):
        cls = type(self)
        if len(name) == 1:   # 如果屬性是單個(gè)字母
            pos = cls.shortcut_name.find(name)    
            if 0 <= pos < len(self._components):  # 判斷是不是xyzt中的一個(gè)
                return self._components[pos]   
        msg = "{.__name__!r} object has no attribute {!r}"  # 想要獲取其他屬性時(shí)則拋出異常
        raise AttributeError(msg.format(cls, name))

    def __setattr__(self, name, value):
        cls = type(self)
        if len(name) == 1:   # 不允許創(chuàng)建單字母實(shí)例屬性,即便是x,y,z,t
            if name in cls.shortcut_name:  # 如果name是xyzt中的一個(gè),設(shè)置特殊的錯(cuò)誤信息
                error = "readonly attibute {attr_name!r}"
            elif name.islower():  # 為小寫字母設(shè)置特殊的錯(cuò)誤信息
                error = "can"t set attributes "a" to "z" in {cls_name!r}"
            else:
                error = ""
            if error:   # 當(dāng)用戶試圖動(dòng)態(tài)創(chuàng)建屬性時(shí)拋出異常
                msg = error.format(cls_name=cls.__name__, attr_name=name)
                raise AttributeError(msg)
        super().__setattr__(name, value)   

解釋:

屬性查找失敗后,解釋器會(huì)調(diào)用__getattr__方法。簡單來說,對(duì)my_obj.x表達(dá)式,Python會(huì)檢查my_obj實(shí)例有沒有名為x實(shí)例屬性;如果沒有,則到它所屬的類中查找有沒有名為x類屬性;如果還是沒有,則順著繼承樹繼續(xù)查找。如果依然找不到,則會(huì)調(diào)用my_obj所屬類中定義的__getattr__方法,傳入self和屬性名的字符串形式(如"x");

__getattr____setattr_方法一般同時(shí)定義,否則對(duì)象的行為很容易出現(xiàn)不一致。比如,如果這里定義__getattr__方法,則會(huì)出現(xiàn)如下尷尬的代碼:

# 代碼5.2
>>> v = Vector(range(5))
>>> v
Vector([0.0, 1.0, 2.0, 3.0, 4.0])
>>> v.x
0.0
>>> v.x = 10  # 按理說這里應(yīng)該報(bào)錯(cuò)才對(duì),因?yàn)椴辉试S修改
>>> v.x
10
>>> v  # 其實(shí)是v創(chuàng)建了新實(shí)例屬性x,這也是為什么我們要定義__setattr__
Vector([0.0, 1.0, 2.0, 3.0, 4.0])   # 行為不一致

我們沒有禁止動(dòng)態(tài)添加屬性,只是禁止為單個(gè)字母屬性賦值,如果屬性名的長度大于1,這樣的屬性是可以動(dòng)態(tài)添加的;

如果你看過上一篇文章,那么你可能會(huì)想到用__slots__來禁止添加屬性,但我們這里仍然選擇實(shí)現(xiàn)__setattr__來實(shí)現(xiàn)此功能。__slots__屬性最好只用于節(jié)省內(nèi)存,而且僅在內(nèi)存嚴(yán)重不足時(shí)才用它,別為了秀操作而寫一些別人看著很別扭的代碼(只寫給自己看的除外)。

6. 第4版Vector:散列和快速等值測試

目前這個(gè)Vector是不可散列的,現(xiàn)在我們來實(shí)現(xiàn)__hash__方法。具體方法和上一篇一樣,也是用各個(gè)分量的哈希值進(jìn)行異或運(yùn)算,由于Vector的分量可能很多,這里我們使用functools.reduce函數(shù)來歸約異或值。同時(shí),我們還將改寫之前那個(gè)簡潔版的__eq__,使其更高效(至少對(duì)大型向量來說更高效):

# 代碼6,請自行導(dǎo)入所需的模塊
class Vector:
    -- snip --
    def __hash__(self):
        hashs = (hash(x) for x in self._components)   # 先求各個(gè)分量的哈希值
        return functools.reduce(operator.xor, hashs, 0)  # 然后將所有哈希值歸約成一個(gè)值

    def __eq__(self, other): # 不用像之前那樣:生成元組只為使用元組的__eq__方法
        return len(self) == len(self) and all(a == b for a, b in zip(self, other))

解釋:

此處的__hash__方法實(shí)際上執(zhí)行的是一個(gè)映射歸約的過程。每個(gè)分量被映射成了它們的哈希值,這些哈希值再歸約成一個(gè)值;

這里的functool.reduce傳入了第三個(gè)參數(shù),并且建議最好傳入第三個(gè)參數(shù)。傳入第三個(gè)參數(shù)能避免這個(gè)異常:TypeError: reduce() of empty sequence with no initial value。如果序列為空,第三個(gè)參數(shù)就是返回值;否則,在歸約中它將作為第一個(gè)參數(shù);

__eq__方法中先比較兩序列的長度并不僅僅是一種捷徑。zip函數(shù)并行遍歷多個(gè)可迭代對(duì)象,如果其中一個(gè)耗盡,它會(huì)立即停止生成值,而且不發(fā)出警告;

補(bǔ)充一個(gè)小知識(shí):zip函數(shù)和文件壓縮沒有關(guān)系,它的名字取自拉鏈頭(zipper fastener),這個(gè)小物件把兩個(gè)拉鏈條的鏈牙要合在一起,是不是很形象?
7. 第5版Vector:格式化

Vector2d中,當(dāng)傳入"p"時(shí),以極坐標(biāo)的形式格式化數(shù)據(jù);由于Vector的維度可能大于2,現(xiàn)在,當(dāng)傳入?yún)?shù)"h"時(shí),我們使用球面坐標(biāo)格式化數(shù)據(jù),即""。同時(shí),還需要定義兩個(gè)輔助方法:

angle(n),用于計(jì)算某個(gè)角坐標(biāo);

angles(),返回由所有角坐標(biāo)構(gòu)成的可迭代對(duì)象。

至于這兩個(gè)的數(shù)學(xué)原理就不解釋了。以下是最后要添加的代碼:

# 代碼7
class Vector:
    -- snip --
    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
        return a

    def angles(self):
        return (self.angle(n) for n in range(1, len(self)))

    def __format__(self, format_spec=""):
        if format_spec.endswith("h"):   # 如果格式說明符以"h"結(jié)尾
            format_spec = format_spec[:-1]   # 格式說明符前面部分保持不變
            coords = itertools.chain([abs(self)], self.angles())  # 
            outer_fmt = "<{}>"
        else:
            coords = self
            outer_fmt = "({})"
        components = (format(c, format_spec) for c in coords)
        return outer_fmt.format(", ".join(components))

itertools.chain函數(shù)生成生成器表達(dá)式,將多個(gè)可迭代對(duì)象連接成在一起進(jìn)行迭代。關(guān)于生成器的更多內(nèi)容將在以后的文章中介紹。

至此,多維Vector暫時(shí)告一段落。


迎大家關(guān)注我的微信公眾號(hào)"代碼港" & 個(gè)人網(wǎng)站 www.vpointer.net ~

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/41895.html

相關(guān)文章

  • Python序列修改、列和切片

    摘要:一基本的序列協(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é)議的...

    jubincn 評(píng)論0 收藏0
  • 流暢python讀書筆記-第十章-序列修改、列和切片

    摘要:例如,的序列協(xié)議只需要和兩個(gè)方法。任何類如,只要使用標(biāo)準(zhǔn)的簽名和語義實(shí)現(xiàn)了這兩個(gè)方法,就能用在任何期待序列的地方。方法開放了內(nèi)置序列實(shí)現(xiàn)的棘手邏輯,用于優(yōu)雅地處理缺失索引和負(fù)數(shù)索引,以及長度超過目標(biāo)序列的切片。 序列的修改、散列和切片 接著造Vector2d類 要達(dá)到的要求 為了編寫Vector(3, 4) 和 Vector(3, 4, 5) 這樣的代碼,我們可以讓 init 法接受任...

    cpupro 評(píng)論0 收藏0
  • Python入門學(xué)習(xí)筆記匯總

    摘要:導(dǎo)語本文章匯總了本人在學(xué)習(xí)基礎(chǔ)之緒論篇數(shù)據(jù)結(jié)構(gòu)篇函數(shù)篇面向?qū)ο笃刂屏鞒唐驮幊唐獙W(xué)習(xí)筆記的鏈接,打算入門的朋友們可以按需查看并交流。 導(dǎo)語:本文章匯總了本人在學(xué)習(xí)Python基礎(chǔ)之緒論篇、數(shù)據(jù)結(jié)構(gòu)篇、函數(shù)篇、面向?qū)ο笃?、控制流程篇和元編程篇學(xué)習(xí)筆記的鏈接,打算入門Python的朋友們可以按需查看并交流。 第一部分:緒論篇 1、Python數(shù)據(jù)模型 第二部分:數(shù)據(jù)結(jié)構(gòu)篇 2、序列構(gòu)成...

    U2FsdGVkX1x 評(píng)論0 收藏0
  • Python學(xué)習(xí)之路28-符合Python風(fēng)格對(duì)象

    摘要:本篇繼續(xù)學(xué)習(xí)之路,實(shí)現(xiàn)更多的特殊方法以讓自定義類的行為跟真正的對(duì)象一樣。之所以要讓向量不可變,是因?yàn)槲覀冊谟?jì)算向量的哈希值時(shí)需要用到和的哈希值,如果這兩個(gè)值可變,那向量的哈希值就能隨時(shí)變化,這將不是一個(gè)可散列的對(duì)象。 《流暢的Python》筆記。本篇是面向?qū)ο髴T用方法的第二篇。前一篇講的是內(nèi)置對(duì)象的結(jié)構(gòu)和行為,本篇?jiǎng)t是自定義對(duì)象。本篇繼續(xù)Python學(xué)習(xí)之路20,實(shí)現(xiàn)更多的特殊方法以讓...

    Eric 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<