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 中,map
、reduce
和 filter
都是 build-in functions(內(nèi)置函數(shù)),返回的都是列表或計算結(jié)果,使用起來很方便;但到了 Python 3,reduce
不再是內(nèi)置函數(shù),而被放入 functools
,map
和 filter
返回的不再是列表,而是可迭代的對象。假如你需要的恰恰是列表,那么使用起來略顯繁瑣。
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
如果不拘泥于 map
、reduce
的形式,實(shí)際上,list comprehension (列表推導(dǎo))和 generator expression(生成器表達(dá)式)可以優(yōu)雅地代替 map
和 filter
:
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 中還提供了 takewhile
、dropwhile
、filterfalse
、groupby
、permutations
、combinations
等諸多函數(shù)式編程中常用的函數(shù)。
其他
另外,Python 不支持 pattern matching,return 語句怎么看怎么像 imperative programming (命令式編程)余孽,這些都限制了 Python 成為一種更好的函數(shù)式編程語言。
綜上所述,相信你對于如何使用 Python 編寫函數(shù)式代碼已經(jīng)入門,考慮到 Python 語言設(shè)計上對函數(shù)式編程的諸多限制,下一步就該考慮放棄了……