當前位置:
首頁 > 知識 > Python 函數式編程:不可變數據結構

Python 函數式編程:不可變數據結構

Python 函數式編程:不可變數據結構

打開今日頭條,查看更多精彩圖片

編譯自: https://opensource.com/article/18/10/functional-programming-python-immutable-data-structures

作者: Moshe Zadka

譯者: David Dai

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

在這個由兩篇文章構成的系列中,我將討論如何將函數式編程方法論中的思想引入至 Python 中,來充分發揮這兩個領域的優勢。

本文(也就是第一篇文章)中,我們將探討不可變數據結構的優勢。第二部分會探討如何在 toolz 庫的幫助下,用 Python 實現高層次的函數式編程理念。

為什麼要用函數式編程?因為變化的東西更難推理。如果你已經確信變化會帶來麻煩,那很棒。如果你還沒有被說服,在文章結束時,你會明白這一點的。

我們從思考正方形和矩形開始。如果我們拋開實現細節,單從介面的角度考慮,正方形是矩形的子類嗎?

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

如何為矩形定義介面?


from zope.interface import Interface

class IRectangle(Interface):

def get_length(self):

"""正方形能做到"""

def get_width(self):

"""正方形能做到"""

def set_dimensions(self, length, width):

"""啊哦"""

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

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


class IRectangle(Interface):

def get_length(self):

"""正方形能做到"""

def get_width(self):

"""正方形能做到"""

def with_dimensions(self, length, width):

"""返回一個新矩形"""

現在,我們可以將正方形視為矩形了。在調用 with_dimensions 時,它可以返回一個新的矩形(它不一定是個正方形),但它本身並沒有變,依然是一個正方形。

這似乎像是個學術問題 —— 直到我們認為正方形和矩形可以在某種意義上看做一個容器的側面。在理解了這個例子以後,我們會處理更傳統的容器,以解決更現實的案例。比如,考慮一下隨機存取數組。

我們現在有 ISquare 和 IRectangle,而且 ISequere 是 IRectangle 的子類。

我們希望把矩形放進隨機存取數組中:


class IArrayOfRectangles(Interface):

def get_element(self, i):

"""返回一個矩形"""

def set_element(self, i, rectangle):

""""rectangle" 可以是任意 IRectangle 對象"""

我們同樣希望把正方形放進隨機存取數組:


class IArrayOfSquare(Interface):

def get_element(self, i):

"""返回一個正方形"""

def set_element(self, i, square):

""""square" 可以是任意 ISquare 對象"""

儘管 ISquare 是 IRectangle 的子集,但沒有任何一個數組可以同時實現 IArrayOfSquare 和 IArrayOfRectangle.

為什麼不能呢?假設 bucket 實現了這兩個類的功能。

>>> rectangle = make_rectangle(3, 4)

>>> bucket.set_element(0, rectangle) # 這是 IArrayOfRectangle 中的合法操作

>>> thing = bucket.get_element(0) # IArrayOfSquare 要求 thing 必須是一個正方形

>>> assert thing.height == thing.width

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AssertionError

無法同時實現這兩類功能,意味著這兩個類無法構成繼承關係,即使 ISquare 是 IRectangle 的子類。問題來自 set_element 方法:如果我們實現一個只讀的數組,那 IArrayOfSquare 就可以是 IArrayOfRectangle 的子類了。

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

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

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

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


@attr.s(frozen=True)

class Rectange(object):

length = attr.ib()

width = attr.ib()

@classmethod

def with_dimensions(cls, length, width):

return cls(length, width)

這是一個正方形:

@attr.s(frozen=True)

class Square(object):

side = attr.ib()

@classmethod

def with_dimensions(cls, length, width):

return Rectangle(length, width)

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

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


too_long = Rectangle(100, 4)

reasonable = attr.evolve(too_long, length=10)

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


# 由整數構成的向量

a = pyrsistent.v(1, 2, 3)

# 並非由整數構成的向量

b = a.set(1, "hello")

儘管 b 不是一個由整數構成的向量,但沒有什麼能夠改變 a 只由整數構成的性質。

如果 a 有一百萬個元素呢?b 會將其中的 999999 個元素複製一遍嗎?Pyrsistent 具有「大 O」性能保證:所有操作的時間複雜度都是 O(log n). 它還帶有一個可選的 C 語言擴展,以在「大 O」性能之上進行提升。

修改嵌套對象時,會涉及到「變換器」的概念:


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 現在將是如下對象的不可變等價物:


{"links": ["github", "twitter"],

"posts": [{"content": "I"m busy",

"title": "no updates"},

{"content": "pretty busy",

"title": "still no updates"}],

"title": "My blog"}

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

當共享行為猖獗時,這會很有用。例如,函數的默認參數:


def silly_sum(a, b, extra=v(1, 2)):

extra = extra.extend([a, b])

return sum(extra)

在本文中,我們了解了為什麼不可變性有助於我們來思考我們的代碼,以及如何在不帶來過大性能負擔的條件下實現它。下一篇,我們將學習如何藉助不可變對象來實現強大的程序結構。



via: https://opensource.com/article/18/10/functional-programming-python-immutable-data-structures

作者: Moshe Zadka 選題: lujun9972 譯者: StdioA 校對: wxy

本文由 LCTT 原創編譯, Linux中國 榮譽推出


點擊「了解更多」可訪問文內鏈接

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Linux技術 的精彩文章:

讓 Python 代碼更易維護的七種武器
8 個用於業餘項目的優秀 Python 庫

TAG:Linux技術 |