17. Python的“函数式”特性的起源

Python的“函数式”特性

原文by Guido van Rossum 2009-4-21 翻译by kant

一、需求

不论别人怎么想,怎么说,我从不认为 Python 受函数式编程语言影响很大。我一直对命令式编程语言更熟悉,比如 C语言和 Algol 68。虽然在 Python 中,函数也是“第一类对象”(first-class objects),但我并不将 Python 看作一门函数式编程语言。

不过在早期,用户们确实希望列表和函数可以支持更多操作。

对列表的一个常用操作,就是根据一个函数,产生从一个列表到一个新列表的映射。比如说:


    ''def square(x):
    '' return x*x
    ''vals = [1234]
    ''newvals = []
    ''for v in vals:
    '' newvals.append(square(x))

在函数式编程语言,比如 Lisp 和 Scheme 中,类似操作可以直接使用内置函数。因此,熟悉这些语言的早期 Python 用户就会自己实现类似函数。比如说:


    ''def map(fs):
    '' result = []
    '' for x in s:
    '' result.append(f(x))
    '' return result
    ''def square(x):
    '' return x*x
    ''vals = [1234]
    ''newvals = map(squarevals)

不过,很多用户并不想为类似操作另外写一个代码块来定义函数。在 Lisp 之类的语言中,用户可以在进行映射的时候直接进行函数定义。比如说,在 Scheme 中,你可以在一个映射语句中创建一个匿名函数:


    ''(map(lambda(x) (*x x)) '(1 2 3 4))

而在 Python 中,虽然函数是第一等对象,却并没有类似的创建匿名函数的机制。


二、方案

1993年末,Python 用户们已经发明了各种创建匿名函数的方法,以及进行列表操作的函数,比如 map()、filter() 、reduce() 等。比如,Mark Lutz(《Programming Python》的作者,译注:这本书好像没有中文版,大家可能对他的另一本书更熟悉,《Python学习手册(Learning Python)》)曾贴出用 exec 函数创建匿名的方法:


    ''def genfunc(argsexpr):
    '' exec('def f(' + args + '): return ' + expr)
    '' return eval('f')
    ''# 用法示例
    ''vals = [1234]
    ''newvals = map(genfunc('x' 'x*x')vals)

后来, Tim Peters 提出了一种简化方案,于是,用户可以采用以下代码:


    ''vals = [1234]
    ''newvals = map(func('x: x*x')vals)

显然,大家对类似函数有强烈的需求,而用 exec 函数执行代码字符串的方法似乎有点“奇技淫巧”的意思。因此,1994年 1月, map()、filter()、和 reduce() 函数被加入了标准库,并引入 lambda 操作,使用户可以以更直白的方式创建匿名函数。比如说:


    ''vals = [1234]
    ''newvals = map(lambda x:x*xvals)

当时是某位贡献者提供的实现这些特性的代码,可惜我忘记具体是谁了,SVN 日志中也没有相关信息。如果当时的作者看到这段的话,记得评论一下!(更新:根据当时的提案记录,很明显,这些代码是 Amrit Prem 贡献的,他是一位多产的早期贡献者)

其实我并不是很喜欢“lambda”这个词,但也没什么好的选择,因此一直沿用至今。毕竟,这是最初的代码贡献者的选择,而在那个时候,不知道是好是坏,语言的重大改变不像现在那样需要经过大量讨论。


三、问题

lambda 一开始只是用于创建匿名函数的一个句法,但选用这个词却导致了一些其它问题。比如说,一些熟悉函数式编程语言的用户会期望它的作用和在其它函数式编程语言中一样,然后发现 Python 中并没有实现更多进阶特性。

举例来说,Python 中的 lambda 并不支持当前作用域中的变量,因为 lambda 函数认为变量 a 未定义,下面的代码会报错:


    ''def spam(s):
    '' a = 4
    '' r = map(lambda x: a*xs)

也有处理这个问题的方案,但要给 lambda 函数设置默认参数并传递隐藏参数,并不直观:


    ''def spam(s):
    '' a = 4
    '' r = map(lambda x a=a: a*xs)

要“正确”地解决这个问题,就要让匿名函数隐式引用当前环境,这也就是函数式编程语言中所谓的“闭包”。直到 2.2 版本,Python 才支持这个特性(在 2.1 版本中,用户可以通过“from the future”引入此特性)。

奇怪的是,当初导致 Python 引入 lambda 和其它一些函数式特性的几个函数,map、filter 和 reduce,却在之后很大程度上被列表推导式和生成器表达式所取代。事实上,在 Python 3 中,reduce 函数已经不是列表的内置函数了。(大家不要激动,labmda、map 和 filter 还在 :-)


四、总结

值得注意的是,虽然我不把 Python 看做函数式编程语言,引入闭包概念却在一些其它特性的开发中起到了重要作用。比如说,一些新式类的特性、Python 装饰器,以及一些其它现代特性都依赖于闭包概念。

最后,我想说的是,虽然这些年来 Python 引入了一些函数式编程语言的特性,但和“真正的”函数式编程语言相比,Python 还是缺乏一些特性的。比如说,Python 并没有进行特定方向的优化(比如尾部递归优化)。

总的来说,Python 是一门动态语言,不可能像函数式编程语言,比如 Haskell 或 ML 那样执行一些特定的编译优化。这并不是什么问题。"


发表评论

评论列表,共 0 条评论