您好,登錄后才能下訂單哦!
這篇文章主要講解了“Python不可變數(shù)據(jù)結(jié)構(gòu)舉例分析”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Python不可變數(shù)據(jù)結(jié)構(gòu)舉例分析”吧!
我們從思考正方形和矩形開(kāi)始。如果我們拋開(kāi)實(shí)現(xiàn)細(xì)節(jié),單從接口的角度考慮,正方形是矩形的子類(lèi)嗎?
子類(lèi)的定義基于里氏替換原則。一個(gè)子類(lèi)必須能夠完成超類(lèi)所做的一切。
如何為矩形定義接口?
from zope.interface import Interface class IRectangle(Interface): def get_length(self): """正方形能做到""" def get_width(self): """正方形能做到""" def set_dimensions(self, length, width): """啊哦"""
如果我們這么定義,那正方形就不能成為矩形的子類(lèi):如果長(zhǎng)度和寬度不等,它就無(wú)法對(duì) set_dimensions
方法做出響應(yīng)。
另一種方法,是選擇將矩形做成不可變對(duì)象。
class IRectangle(Interface): def get_length(self): """正方形能做到""" def get_width(self): """正方形能做到""" def with_dimensions(self, length, width): """返回一個(gè)新矩形"""
現(xiàn)在,我們可以將正方形視為矩形了。在調(diào)用 with_dimensions
時(shí),它可以返回一個(gè)新的矩形(它不一定是個(gè)正方形),但它本身并沒(méi)有變,依然是一個(gè)正方形。
這似乎像是個(gè)學(xué)術(shù)問(wèn)題 —— 直到我們認(rèn)為正方形和矩形可以在某種意義上看做一個(gè)容器的側(cè)面。在理解了這個(gè)例子以后,我們會(huì)處理更傳統(tǒng)的容器,以解決更現(xiàn)實(shí)的案例。比如,考慮一下隨機(jī)存取數(shù)組。
我們現(xiàn)在有 ISquare
和 IRectangle
,而且 ISequere
是 IRectangle
的子類(lèi)。
我們希望把矩形放進(jìn)隨機(jī)存取數(shù)組中:
class IArrayOfRectangles(Interface): def get_element(self, i): """返回一個(gè)矩形""" def set_element(self, i, rectangle): """'rectangle' 可以是任意 IRectangle 對(duì)象"""
我們同樣希望把正方形放進(jìn)隨機(jī)存取數(shù)組:
class IArrayOfSquare(Interface): def get_element(self, i): """返回一個(gè)正方形""" def set_element(self, i, square): """'square' 可以是任意 ISquare 對(duì)象"""
盡管 ISquare
是 IRectangle
的子集,但沒(méi)有任何一個(gè)數(shù)組可以同時(shí)實(shí)現(xiàn) IArrayOfSquare
和 IArrayOfRectangle
.
為什么不能呢?假設(shè) bucket
實(shí)現(xiàn)了這兩個(gè)類(lèi)的功能。
>>> rectangle = make_rectangle(3, 4)>>> bucket.set_element(0, rectangle) # 這是 IArrayOfRectangle 中的合法操作>>> thing = bucket.get_element(0) # IArrayOfSquare 要求 thing 必須是一個(gè)正方形>>> assert thing.height == thing.widthTraceback (most recent call last): File "<stdin>", line 1, in <module>AssertionError
無(wú)法同時(shí)實(shí)現(xiàn)這兩類(lèi)功能,意味著這兩個(gè)類(lèi)無(wú)法構(gòu)成繼承關(guān)系,即使 ISquare
是 IRectangle
的子類(lèi)。問(wèn)題來(lái)自 set_element
方法:如果我們實(shí)現(xiàn)一個(gè)只讀的數(shù)組,那 IArrayOfSquare
就可以是 IArrayOfRectangle
的子類(lèi)了。
在可變的 IRectangle
和可變的 IArrayOf*
接口中,可變性都會(huì)使得對(duì)類(lèi)型和子類(lèi)的思考變得更加困難 —— 放棄變換的能力,意味著我們的直覺(jué)所希望的類(lèi)型間關(guān)系能夠成立了。
可變性還會(huì)帶來(lái)作用域方面的影響。當(dāng)一個(gè)共享對(duì)象被兩個(gè)地方的代碼改變時(shí),這種問(wèn)題就會(huì)發(fā)生。一個(gè)經(jīng)典的例子是兩個(gè)線程同時(shí)改變一個(gè)共享變量。不過(guò)在單線程程序中,即使在兩個(gè)相距很遠(yuǎn)的地方共享一個(gè)變量,也是一件簡(jiǎn)單的事情。從 Python 語(yǔ)言的角度來(lái)思考,大多數(shù)對(duì)象都可以從很多位置來(lái)訪問(wèn):比如在模塊全局變量,或在一個(gè)堆棧跟蹤中,或者以類(lèi)屬性來(lái)訪問(wèn)。
如果我們無(wú)法對(duì)共享做出約束,那我們可能要考慮對(duì)可變性來(lái)進(jìn)行約束了。
這是一個(gè)不可變的矩形,它利用了 attr 庫(kù):
@attr.s(frozen=True)class Rectange(object): length = attr.ib() width = attr.ib() @classmethod def with_dimensions(cls, length, width): return cls(length, width)
這是一個(gè)正方形:
@attr.s(frozen=True)class Square(object): side = attr.ib() @classmethod def with_dimensions(cls, length, width): return Rectangle(length, width)
使用 frozen
參數(shù),我們可以輕易地使 attrs
創(chuàng)建的類(lèi)成為不可變類(lèi)型。正確實(shí)現(xiàn) __setitem__
方法的工作都交給別人完成了,對(duì)我們是不可見(jiàn)的。
修改對(duì)象仍然很容易;但是我們不可能改變它的本質(zhì)。
too_long = Rectangle(100, 4)reasonable = attr.evolve(too_long, length=10)
Pyrsistent 能讓我們擁有不可變的容器。
# 由整數(shù)構(gòu)成的向量a = pyrsistent.v(1, 2, 3)# 并非由整數(shù)構(gòu)成的向量b = a.set(1, "hello")
盡管 b
不是一個(gè)由整數(shù)構(gòu)成的向量,但沒(méi)有什么能夠改變 a
只由整數(shù)構(gòu)成的性質(zhì)。
如果 a
有一百萬(wàn)個(gè)元素呢?b
會(huì)將其中的 999999 個(gè)元素復(fù)制一遍嗎?Pyrsistent
具有“大 O”性能保證:所有操作的時(shí)間復(fù)雜度都是 O(log n)
. 它還帶有一個(gè)可選的 C 語(yǔ)言擴(kuò)展,以在“大 O”性能之上進(jìn)行提升。
修改嵌套對(duì)象時(shí),會(huì)涉及到“變換器”的概念:
blog = pyrsistent.m( title="My blog", links=pyrsistent.v("github", "twitter"), posts=pyrsistent.v( pyrsistent.m(title="no updates", content="I'm busy"), pyrsistent.m(title="still no updates", content="still busy")))new_blog = blog.transform(["posts", 1, "content"], "pretty busy")
new_blog
現(xiàn)在將是如下對(duì)象的不可變等價(jià)物:
{'links': ['github', 'twitter'], 'posts': [{'content': "I'm busy", 'title': 'no updates'}, {'content': 'pretty busy', 'title': 'still no updates'}], 'title': 'My blog'}
不過(guò) blog
依然不變。這意味著任何擁有舊對(duì)象引用的人都沒(méi)有受到影響:轉(zhuǎn)換只會(huì)有局部效果。
當(dāng)共享行為猖獗時(shí),這會(huì)很有用。例如,函數(shù)的默認(rèn)參數(shù):
def silly_sum(a, b, extra=v(1, 2)): extra = extra.extend([a, b]) return sum(extra)
感謝各位的閱讀,以上就是“Python不可變數(shù)據(jù)結(jié)構(gòu)舉例分析”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Python不可變數(shù)據(jù)結(jié)構(gòu)舉例分析這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。