Python 對函數(shù)式編程的支持

Python does not promote functional programming even though it works fairly well.

Anonymous function

匿名函數(shù)是一種語法糖(syntactic sugar):所有使用匿名函數(shù)的地方,都可以用普通函數(shù)代替,少了它程序一樣寫,匿名函數(shù)只是讓這一過程更加簡便可讀。我國程序員們?yōu)榱私o小函數(shù)起一個能準(zhǔn)確描述功能的英文名稱,做歸納、查字典,花的時間比實(shí)現(xiàn)函數(shù)本身還要長。匿名函數(shù)一出,問題迎刃而解。不過從簡便可讀的角度來講,Python 的匿名函數(shù),即 lambda 函數(shù),真是有點(diǎn)名不副實(shí)。lambda 關(guān)鍵字包含6個字母,外加一個空格,本身就很長,寫出來的匿名函數(shù)更長,嚴(yán)重影響閱讀。比如下面這個:

map(lambda x: x * x, lst)

同樣的功能,Scala 寫出來就清晰了不少:

lst map (x => x * x)

另外,lambda 函數(shù)體只支持一條語句,這也限制了它的功能。不過從另一方面來說,需要兩條以上語句實(shí)現(xiàn)的函數(shù)就不該再作為匿名函數(shù)了。

Currying & partial application

Python 并不原生支持柯里化(currying),如果你一定要,那也可以有。

舉例來說,你有一個函數(shù) f:

def f(a, b, c):
    print(a, b, c)

如果要將其柯里化,你可以改寫成這樣:

def f(a):
    def g(b):
        def h(c):
            print(a, b, c)
        return h
    return g

In [2]: f(1)(2)(3)
1 2 3

In [3]: g = f(4)

In [4]: g(5)(6)
4 5 6

得益于函數(shù)在 Python 中一等公民的地位,即所謂 first-class function,你可以在函數(shù)中返回另一個函數(shù),柯里化也由此實(shí)現(xiàn)。《Currying in Python》一文提供了一種將普通函數(shù)柯里化的方法。

Partial application(偏函數(shù))與柯里化是相關(guān)但不同的概念。你可以用 functools.partial 得到一個函數(shù)的偏函數(shù):

In [1]: def f(a, b, c):
   ...:     print(a, b, c)
   ...:

In [2]: from functools import partial

In [3]: g = partial(partial(f, 1), 2)

In [4]: g(3)
1 2 3

Recursion & tail recursion

Python 不支持對尾遞歸函數(shù)的優(yōu)化(tail recursion elimination, TRE),也根本不建議程序員在生產(chǎn)代碼中使用遞歸。

摘錄一段 Python 領(lǐng)導(dǎo)核心 Guido van Rossum 關(guān)于尾遞歸的講話,供大家學(xué)習(xí)領(lǐng)會:

So let me defend my position (which is that I don't want TRE in the language). If you want a short answer, it's simply unpythonic.

Lazy evaluation

生成器(generator)就是 Python 中惰性求值(lazy evaluation)的一個例子:一個值只有當(dāng)請求到來時,才會被計算。生成器的使用,可以看我的另一篇文章《用 Python 寫一個函數(shù)式編程風(fēng)格的斐波那契序列生成器》

但是 Python 沒有一些函數(shù)式編程語言中那種精確到每個值的惰性計算。比如在 Scala 中,你可以用關(guān)鍵字 lazy 定義一個值:

scala> lazy val a = 2 * 2
a: Int = <lazy>

scala> a
res0: Int = 4

直到第一次調(diào)用,值 a 才會被計算出來。

Python 可以曲折地實(shí)現(xiàn)類似的惰性計算特性。Python Cookbook 第三版 8.10 節(jié)展示了如何利用描述符(descriptor)實(shí)現(xiàn)類屬性的惰性計算:

class lazyproperty(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            value = self.func(instance) 
            setattr(instance, self.func.__name__, value) 
            return value

定義好的 lazyproperty 作為裝飾器(decorator),被裝飾的屬性就只有在被調(diào)用時才會求值,求得的值會被緩存供以后使用:

class Circle(object):
    def __init__(self, radius):
        self.radius = radius

    @lazyproperty
    def area(self):
        print('Computing area')
        return math.pi * self.radius ** 2

    @lazyproperty
    def perimeter(self):
        print('Computing perimeter')
        return 2 * math.pi * self.radius

In [16]: import math

In [17]: c = Circle(4.0)

In [18]: c.area
Computing area
Out[18]: 50.26548245743669

In [19]: c.area
Out[19]: 50.26548245743669

map, reduce & filter

好消息是,Python 支持這些基本的操作;而壞消息是,Python 不建議你使用它們。

在 Python 2 中,mapreducefilter 都是 build-in functions(內(nèi)置函數(shù)),返回的都是列表或計算結(jié)果,使用起來很方便;但到了 Python 3,reduce 不再是內(nèi)置函數(shù),而被放入 functoolsmapfilter 返回的不再是列表,而是可迭代的對象。假如你需要的恰恰是列表,那么使用起來略顯繁瑣。

In [1]: from platform import python_version

In [2]: print('Python', python_version())
Python 3.5.2

In [3]: n1 = [1, 2, 3]

In [4]: n2 = [4, 5, 6]

In [5]: import operator

In [6]: map(operator.add, n1, n2)
Out[6]: <map at 0x104749748>

In [7]: list(map(operator.add, n1, n2))
Out[7]: [5, 7, 9]

In [8]: import functools

In [9]: functools.reduce(operator.add, n1)
Out[9]: 6

如果不拘泥于 mapreduce 的形式,實(shí)際上,list comprehension (列表推導(dǎo))和 generator expression(生成器表達(dá)式)可以優(yōu)雅地代替 mapfilter

In [10]: [i + j for i, j in zip(n1, n2)] # list(map(operator.add, n1, n2))
Out[10]: [5, 7, 9]

In [11]: [i for i in n1 if i > 1] # list(filter(lambda x: x > 1, n1))
Out[11]: [2, 3]

一個 for 循環(huán)也總是可以代替 reduce,當(dāng)然,你得多寫幾行代碼,看上去也不那么 functional 了。

此外,itertools 中還提供了 takewhiledropwhilefilterfalsegroupbypermutationscombinations 等諸多函數(shù)式編程中常用的函數(shù)。

其他

另外,Python 不支持 pattern matching,return 語句怎么看怎么像 imperative programming (命令式編程)余孽,這些都限制了 Python 成為一種更好的函數(shù)式編程語言。


綜上所述,相信你對于如何使用 Python 編寫函數(shù)式代碼已經(jīng)入門,考慮到 Python 語言設(shè)計上對函數(shù)式編程的諸多限制,下一步就該考慮放棄了……

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內(nèi)容