日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Python函數(shù)式編程:不可變數(shù)據(jù)結(jié)構(gòu)

不可變性可以幫助我們更好地理解我們的代碼。下面我將講述如何在不犧牲性能的條件下來實(shí)現(xiàn)它。

成都創(chuàng)新互聯(lián)公司2013年開創(chuàng)至今,先為慶元等服務(wù)建站,慶元等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為慶元企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

在這個(gè)由兩篇文章構(gòu)成的系列中,我將討論如何將函數(shù)式編程方法論中的思想引入至 Python 中,來充分發(fā)揮這兩個(gè)領(lǐng)域的優(yōu)勢。

本文(也就是***篇文章)中,我們將探討不可變數(shù)據(jù)結(jié)構(gòu)的優(yōu)勢。第二部分會探討如何在 toolz 庫的幫助下,用 Python 實(shí)現(xiàn)高層次的函數(shù)式編程理念。

為什么要用函數(shù)式編程?因?yàn)樽兓臇|西更難推理。如果你已經(jīng)確信變化會帶來麻煩,那很棒。如果你還沒有被說服,在文章結(jié)束時(shí),你會明白這一點(diǎn)的。

我們從思考正方形和矩形開始。如果我們拋開實(shí)現(xiàn)細(xì)節(jié),單從接口的角度考慮,正方形是矩形的子類嗎?

子類的定義基于里氏替換原則。一個(gè)子類必須能夠完成超類所做的一切。

如何為矩形定義接口?

 
 
 
  1. from zope.interface import Interface
  2.  
  3. class IRectangle(Interface):
  4.     def get_length(self):
  5.         """正方形能做到"""
  6.     def get_width(self):
  7.         """正方形能做到"""
  8.     def set_dimensions(self, length, width):
  9.         """啊哦"""

如果我們這么定義,那正方形就不能成為矩形的子類:如果長度和寬度不等,它就無法對 set_dimensions 方法做出響應(yīng)。

另一種方法,是選擇將矩形做成不可變對象。

 
 
 
  1. class IRectangle(Interface):
  2.     def get_length(self):
  3.         """正方形能做到"""
  4.     def get_width(self):
  5.         """正方形能做到"""
  6.     def with_dimensions(self, length, width):
  7.         """返回一個(gè)新矩形"""

現(xiàn)在,我們可以將正方形視為矩形了。在調(diào)用 with_dimensions 時(shí),它可以返回一個(gè)新的矩形(它不一定是個(gè)正方形),但它本身并沒有變,依然是一個(gè)正方形。

這似乎像是個(gè)學(xué)術(shù)問題 —— 直到我們認(rèn)為正方形和矩形可以在某種意義上看做一個(gè)容器的側(cè)面。在理解了這個(gè)例子以后,我們會處理更傳統(tǒng)的容器,以解決更現(xiàn)實(shí)的案例。比如,考慮一下隨機(jī)存取數(shù)組。

我們現(xiàn)在有 ISquareIRectangle,而且 ISequereIRectangle 的子類。

我們希望把矩形放進(jìn)隨機(jī)存取數(shù)組中:

 
 
 
  1. class IArrayOfRectangles(Interface):
  2.     def get_element(self, i):
  3.         """返回一個(gè)矩形"""
  4.     def set_element(self, i, rectangle):
  5.         """'rectangle' 可以是任意 IRectangle 對象"""

我們同樣希望把正方形放進(jìn)隨機(jī)存取數(shù)組:

 
 
 
  1. class IArrayOfSquare(Interface):
  2.     def get_element(self, i):
  3.         """返回一個(gè)正方形"""
  4.     def set_element(self, i, square):
  5.         """'square' 可以是任意 ISquare 對象"""

盡管 ISquareIRectangle 的子集,但沒有任何一個(gè)數(shù)組可以同時(shí)實(shí)現(xiàn) IArrayOfSquareIArrayOfRectangle.

為什么不能呢?假設(shè) bucket 實(shí)現(xiàn)了這兩個(gè)類的功能。

 
 
 
  1. >>> rectangle = make_rectangle(3, 4)
  2. >>> bucket.set_element(0, rectangle) # 這是 IArrayOfRectangle 中的合法操作
  3. >>> thing = bucket.get_element(0) # IArrayOfSquare 要求 thing 必須是一個(gè)正方形
  4. >>> assert thing.height == thing.width
  5. Traceback (most recent call last):
  6.   File "", line 1, in
  7. AssertionError

無法同時(shí)實(shí)現(xiàn)這兩類功能,意味著這兩個(gè)類無法構(gòu)成繼承關(guān)系,即使 ISquareIRectangle 的子類。問題來自 set_element 方法:如果我們實(shí)現(xiàn)一個(gè)只讀的數(shù)組,那 IArrayOfSquare 就可以是 IArrayOfRectangle 的子類了。

在可變的 IRectangle 和可變的 IArrayOf* 接口中,可變性都會使得對類型和子類的思考變得更加困難 —— 放棄變換的能力,意味著我們的直覺所希望的類型間關(guān)系能夠成立了。

可變性還會帶來作用域方面的影響。當(dāng)一個(gè)共享對象被兩個(gè)地方的代碼改變時(shí),這種問題就會發(fā)生。一個(gè)經(jīng)典的例子是兩個(gè)線程同時(shí)改變一個(gè)共享變量。不過在單線程程序中,即使在兩個(gè)相距很遠(yuǎn)的地方共享一個(gè)變量,也是一件簡單的事情。從 Python 語言的角度來思考,大多數(shù)對象都可以從很多位置來訪問:比如在模塊全局變量,或在一個(gè)堆棧跟蹤中,或者以類屬性來訪問。

如果我們無法對共享做出約束,那我們可能要考慮對可變性來進(jìn)行約束了。

這是一個(gè)不可變的矩形,它利用了 attr 庫:

 
 
 
  1. @attr.s(frozen=True)
  2. class Rectange(object):
  3.     length = attr.ib()
  4.     width = attr.ib()
  5.     @classmethod
  6.     def with_dimensions(cls, length, width):
  7.         return cls(length, width)

這是一個(gè)正方形:

 
 
 
  1. @attr.s(frozen=True)
  2. class Square(object):
  3.     side = attr.ib()
  4.     @classmethod
  5.     def with_dimensions(cls, length, width):
  6.         return Rectangle(length, width)

使用 frozen 參數(shù),我們可以輕易地使 attrs 創(chuàng)建的類成為不可變類型。正確實(shí)現(xiàn) __setitem__ 方法的工作都交給別人完成了,對我們是不可見的。

修改對象仍然很容易;但是我們不可能改變它的本質(zhì)。

 
 
 
  1. too_long = Rectangle(100, 4)
  2. reasonable = attr.evolve(too_long, length=10)

Pyrsistent 能讓我們擁有不可變的容器。

 
 
 
  1. # 由整數(shù)構(gòu)成的向量
  2. a = pyrsistent.v(1, 2, 3)
  3. # 并非由整數(shù)構(gòu)成的向量
  4. b = a.set(1, "hello")

盡管 b 不是一個(gè)由整數(shù)構(gòu)成的向量,但沒有什么能夠改變 a 只由整數(shù)構(gòu)成的性質(zhì)。

如果 a 有一百萬個(gè)元素呢?b 會將其中的 999999 個(gè)元素復(fù)制一遍嗎?Pyrsistent 具有“大 O”性能保證:所有操作的時(shí)間復(fù)雜度都是 O(log n). 它還帶有一個(gè)可選的 C 語言擴(kuò)展,以在“大 O”性能之上進(jìn)行提升。

修改嵌套對象時(shí),會涉及到“變換器”的概念:

 
 
 
  1. blog = pyrsistent.m(
  2.     title="My blog",
  3.     links=pyrsistent.v("github", "twitter"),
  4.     posts=pyrsistent.v(
  5.         pyrsistent.m(title="no updates",
  6.                      content="I'm busy"),
  7.         pyrsistent.m(title="still no updates",
  8.                      content="still busy")))
  9. new_blog = blog.transform(["posts", 1, "content"],
  10.                           "pretty busy")

new_blog 現(xiàn)在將是如下對象的不可變等價(jià)物:

 
 
 
  1. {'links': ['github', 'twitter'],
  2.  'posts': [{'content': "I'm busy",
  3.             'title': 'no updates'},
  4.            {'content': 'pretty busy',
  5.             'title': 'still no updates'}],
  6.  'title': 'My blog'}

不過 blog 依然不變。這意味著任何擁有舊對象引用的人都沒有受到影響:轉(zhuǎn)換只會有局部效果。

當(dāng)共享行為猖獗時(shí),這會很有用。例如,函數(shù)的默認(rèn)參數(shù):

 
 
 
  1. def silly_sum(a, b, extra=v(1, 2)):
  2.     extra = extra.extend([a, b])
  3.     return sum(extra)

在本文中,我們了解了為什么不可變性有助于我們來思考我們的代碼,以及如何在不帶來過大性能負(fù)擔(dān)的條件下實(shí)現(xiàn)它。下一篇,我們將學(xué)習(xí)如何借助不可變對象來實(shí)現(xiàn)強(qiáng)大的程序結(jié)構(gòu)。


文章題目:Python函數(shù)式編程:不可變數(shù)據(jù)結(jié)構(gòu)
文章來源:http://www.dlmjj.cn/article/dpjcego.html