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ù)起一個能準確描述功能的英文名稱,做歸納、查字典,花的時間比實現(xiàn)函數(shù)本身還要長。匿名函數(shù)一出,問題迎刃而解。不過從簡便可讀的角度來講,Python 的匿名函數(shù),即 lambda 函數(shù),真是有點名不副實。lambda 關鍵字包含6個字母,外加一個空格,本身就很長,寫出來的匿名函數(shù)更長,嚴重影響閱讀。比如下面這個:

map(lambda x: x * x, lst)

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

lst map (x => x * x)

另外,lambda 函數(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ù),柯里化也由此實現(xiàn)。《Currying in Python》一文提供了一種將普通函數(shù)柯里化的方法。

Partial application(偏函數(shù))與柯里化是相關但不同的概念。你可以用 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 領導核心 Guido van Rossum 關于尾遞歸的講話,供大家學習領會:

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)的一個例子:一個值只有當請求到來時,才會被計算。生成器的使用,可以看我的另一篇文章《用 Python 寫一個函數(shù)式編程風格的斐波那契序列生成器》

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

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

scala> a
res0: Int = 4

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

Python 可以曲折地實現(xiàn)類似的惰性計算特性。Python Cookbook 第三版 8.10 節(jié)展示了如何利用描述符(descriptor)實現(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ù)),返回的都是列表或計算結果,使用起來很方便;但到了 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 的形式,實際上,list comprehension (列表推導)和 generator expression(生成器表達式)可以優(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,當然,你得多寫幾行代碼,看上去也不那么 functional 了。

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

其他

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


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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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