在计算机科学中,函数式编程是一种通过应用和组合函数来构建程序的编程范式。它是一种声明式编程范式,其中函数定义是将值映射到其他值的表达式树,而不是更新程序运行状态的一系列命令式语句。函数式编程
函数式编程的一个有用的应用出现在调试和性能测量方面上。你正在使用需要每夜都被完全测试或通过衰退,或需要给对潜在改善进行多次迭代计时的函数来工作。你所要做的就是创建一个设置测试环境的诊断函数,然后对有疑问的地方,调用函数。因为系统应该是灵活的, 所以想testee 函数作为参数传入。那么这样的函数对,timeit()和 testit(),可能会对如今的软件开发者有帮助。
示例:Testing Functions (testit.py)
testit()用其参数地调用了一个给定的函数,成功的话,返回一个和那函数返回值打包的 True的返回值,或者 False 和失败的原因。
#!/usr/bin/env python
def testit(func, *nkwargs, **kwargs):
try:
retval = func(*nkwargs, **kwargs)
result = (True, retval)
except Exception, diag:
result = (False, str(diag))
return result
def test():
funcs = (int, long, float)
vals = (1234, 12.34, '1234', '12.34')
for eachFunc in funcs:
print '-' * 20
for eachVal in vals:
retval = testit(eachFunc,eachVal)
if retval[0]:
print '%s(%s) =' % (eachFunc.__name__, `eachVal`), retval[1]
else:
print '%s(%s) = FAILED:' % (eachFunc.__name__, `eachVal`), retval[1]
if __name__ == '__main__':
test()
单元测试函数 test()在一个为 4 个数字的输入集合运行了一个数字转换函数的集合。为了确定这样的功能性,在测试中有两个失败的案例。这里是运行脚本的输出:
$ testit.py
--------------------
int(1234) = 1234 int(12.34)
= 12 int('1234') = 1234
int('12.34') = FAILED: invalid literal for int(): 12.34
--------------------
long(1234) = 1234L
long(12.34) = 12L
long('1234') = 1234L
long('12.34') = FAILED: invalid literal for long(): 12.34
--------------------
float(1234) = 1234.0
float(12.34) = 12.34
float('1234') = 1234.0
float('12.34') = 12.34
Python 不是也不大可能会成为一种函数式编程语言,但是它支持许多有价值的函数式编程语言构建。也有些表现得像函数式编程机制但是从传统上也不能被认为是函数式编程语言的构建。Python提供的以 4 种内建函数和 lambda 表达式的形式出现
python 允许用 lambda 关键字创造匿名函数。匿名是因为不需要以标准的方式来声明,比如说,使用 def 语句。(除非赋值给一个局部变量,这样的对象也不会在任何的名字空间内创建名字.)然而,作为函数,它们也能有参数。一个完整的 lambda“语句”代表了一个表达式,这个表达式的定义体必须和声明放在同一行。我们现在来演示下匿名函数的语法:
lambda [arg1[, arg2, ... argN]]: expression
参数是可选的,如果使用的参数话,参数通常也是表达式的一部分。
用合适的表达式调用一个 lambda 生成一个可以像其他函数一样使用的函数对象。它们可被传入给其他函数,用额外的引用别名化,作为容器对象以及作为可调用的对象被调用(如果需要的话,可以带参数)。当被调用的时候,如过给定相同的参数的话,这些对象会生成一个和相同表达式等价的结果。它们和那些返回等价表达式计算值相同的函数是不能区分的。
在我们看任何一个使用 lambda 的例子之前,我们意欲复习下单行语句,然后展示下 lambda 表达式的相似之处。
def true():
return True
上面的函数没有带任何的参数并且总是返回 True。python 中单行函数可以和标题写在同一行。如果那样的话,我们重写下我们的 true()函数以使其看其来像如下的东西:
def true(): return True
在整这个章节,我们将以这样的方式呈现命名函数,因为这有助于形象化与它们等价的 lamdba表达式。至于我们的 true()函数,使用 lambda 的等价表达式(没有参数,返回一个 True)为:
lambda :True
命名的 true()函数的用法相当的明显,但 lambda 就不是这样。我们仅仅是这样用,或者我们需要在某些地方用它进行赋值吗?一个 lambda 函数自己就是无目地服务,正如在这里看到的:
>>> lambda :True
<function <lambda> at f09ba0>
在上面的例子中,我们简单地用 lambda 创建了一个函数(对象),但是既没有在任何地方保存它,也没有调用它。这个函数对象的引用计数在函数创建时被设置为 True,但是因为没有引用保存下来,计数又回到零,然后被垃圾回收掉。为了保留住这个对象,我们将它保存到一个变量中,以后可以随时调用。现在可能就是一个好机会。
>>> true = lambda :True
>>> true() True
这里用它赋值看起来非常有用。相似地,我们可以把 lambda 表达式赋值给一个如列表和元组的数据结构,其中,基于一些输入标准,我们可以选择哪些函数可以执行,以及参数应该是什么。(在下个部分中,我们将展示如何去使用带函数式编程构建的 lambda 表达式。我们现在来设计一个带 2 个数字或者字符串参数,返回数字之和或者已拼接的字符串的函数。我们先将展示一个标准的函数,然后再是其未命名的等价物。
def add(x, y): return x + y ? lambda x, y: x + y
默认以及可变的参数也是允许的,如下例所示:
def usuallyAdd2(x, y=2): return x+y ? lambda x, y=2: x+y
def showAllAsTuple(*z): return z ? lambda *z: z
看上去是一回事,所以我们现在将通过演示如何能在解释器中尝试这种做法,来努力着让你相信:
>>> a=lambda x,y=2:x+y
>>> a(3)
5
>>> a(3,6)
9
>>> b=lambda *z:z
>>> b(23,'xyz')
(23, 'xyz')
>>> b(42)
(42,)
关于 lambda 最后补充一点:虽然看起来 lambdda 是一个函数的单行版本,但是它不等同于 c++的内联语句,这种语句的目的是由于性能的原因,在调用时绕过函数的栈分配。lambda 表达式运作起来就像一个函数,当被调用时,创建一个框架对象。
lambda 函数可以很好的和使用了这些函数的应用程序结合起来,因为它们都带了一个可执行的函数对象,lambda 表达式提供了迅速创造这些函数的机制。
函数式编程的内建函数
内建函数 | 说明 |
---|---|
apply(func[, nkw][, kw]) | 用可选的参数来调用 func,nkw 为非关键字参数,kw关键字参数;返回值是函数调用的返回值。 |
filter(func, seq) | 调用一个布尔函数 func 来迭代遍历每个 seq 中的元素; 返回一个使 func 返回值为 ture 的元素的序列。 |
map(func, seq1[,seq2…]) | 将函数 func 作用于给定序列(s)的每个元素,并用一个列表来提供返回值;如果 func 为 None, func 表现为一个身份函数,返回一个含有每个序列中元素集合的 n 个元组的列表。 |
reduce(func, seq[, init]) | 将二元函数作用于 seq 序列的元素,每次携带一对(先前的结果以及下一个序列元素),连续的将现有的结果和下雨给值作用在获得的随后的结果上,最后减少我们的序列为一个单一的返回值;如果初始值 init 给定,第一个比较会是 init 和第一个序列元素而不是序列的头两个元素。 |
apply(func [, args [, kwargs ]])
函数用于当函数参数已经存在于一个元组或字典中时,间接地调用函数。args是一个包含将要提供给函数的按位置传递的参数的元组。如果省略了args,任何参数都不会被传递,kwargs是一个包含关键字参数的字典。
apply()的返回值就是func()的返回值,apply()的元素参数是有序的,元素的顺序必须和func()形式参数的顺序一致
def say():
print 'say in'
apply(say)
say in
def say(a, b):
print a, b
apply(say,("hello", "python"))
hello python
def say(a=1,b=2):
print a,b
def haha(**kw):
apply(say,(),kw)
print haha(a='a',b='b')
a b
None
filter(func, seq)
给定一个对象的序列和一个“过滤”函数,每个序列元素都通过这个过滤器进行筛选, 保留函数返回为真的的对象。filter 函数为已知的序列的每个元素调用给定布尔函数。每个 filter 返回的非零(true)值元素添加到一个列表中。返回的对象是一个从原始队列中“过滤后”的队列
如果我们想要用纯 python 编写 filter(),它或许就像这样:
def filter(bool_func, seq):
filtered_seq = []
for eachItem in seq:
if bool_func(eachItem):
filtered_seq.append(eachItem)
return filtered_seq
示例:
from random import randint
def odd(n):
return n % 2
allNums = []
for eachNum in range(9):
allNums.append(randint(1, 99))
print filter(odd, allNums)
$ python oddnogen.py
[9, 33, 55, 65]
$ python oddnogen.py
[39, 77, 39, 71, 1]
Refactoring Pass 1
*第一次重构:我们注意到 odd()是非常的简单的以致能用一个 lambda 表达式替换
from random import randint
allNums = []
for eachNum in range(9):
allNums.append(randint(1, 99))
print filter(lambda n: n%2, allNums)
Refactoring Pass 2
第二次重构:我们使用列表解析
allNums = []
for eachNum in range(9):
allNums.append(randint(1, 99))
print [n for n in allNums if n%2]
Refactoring Pass 3
第三次重构:简化
from random import randint as ri
print [n for n in [ri(1,99) for i in range(9)] if n%2]
map(func, seq1[,seq2...])
内建函数与 filter()相似,因为它也能通过函数来处理序列。然而,不像 filter(), map()将函数调用“映射”到每个序列的元素上,并返回一个含有所有返回值的列表。
如果我们要用 python 编写这个简单形式的 map()如何运作的,如下代码:
def map(func, seq):
mapped_seq = []
for eachItem in seq:
mapped_seq.append(func(eachItem))
return mapped_seq
我们可以列举一些简短的 lambda 函数来展示如何用 map()处理实际数据:
>>> map((lambda x: x+2), [0, 1, 2, 3, 4, 5])
[2, 3, 4, 5, 6, 7]
>>>
>>> map(lambda x: x**2, range(6))
[0, 1, 4, 9, 16, 25]
>>> [x+2 for x in range(6)]
[2, 3, 4, 5, 6, 7]
>>>
>>>[x**2 for x in range(6)]
[0, 1, 4, 9, 16, 25]
有时 map()可以被列表解析取代, 所以这里我们再分析下上面的两个例子。形式更一般的 map()能以多个序列作为其输入。如果是这种情况, 那么 map()会并行地迭代每个序列。在第一次调用时, map()会将每个序列的第一个元素捆绑到一个元组中, 将 func 函数作用到map()上, 当 map()已经完成执行的时候,并将元组的结果返回到 mapped_seq 映射的,最终以整体返回的序列上
这里有些使用带多个序列的 map()的例子
>>> map(lambda x, y: x + y, [1,3,5], [2,4,6])
[3, 7, 11]
>>>
>>> map(lambda x, y: (x+y, x-y), [1,3,5], [2,4,6])
[(3, -1), (7, -1), (11, -1)]
>>>
>>> map(None, [1,3,5], [2,4,6])
[(1, 2), (3, 4), (5, 6)]
上面最后的例子使用了 map()和一个为 None 的函数对象来将不相关的序列归并在一起。这种思想在一个新的内建函数,zip,被加进来之前的 python2.0 是很普遍的。而 zip 是这样做的:
>>> zip([1,3,5], [2,4,6])
[(1, 2), (3, 4), (5, 6)]
函数式编程的最后的一部分是 reduce(),reduce 使用了一个二元函数(一个接收带两个值作为输入,进行了一些计算,然后返回一个值作为输出),一个序列,和一个可选的初始化器,卓有成效地将那个列表的内容“减少”为一个单一的值,如同它的名字一样。在其他的语言中,这种概念也被称作为折叠。
它通过取出序列的头两个元素,将他们传入二元函数来获得一个单一的值来实现。然后又用这个值和序列的下一个元素来获得又一个值,然后继续直到整个序列的内容都遍历完毕以及最后的值会被计算出来为止。
你可以尝试去形象化 reduce 如下面的等同的例子:
reduce(func, [1, 2, 3])=func(func(1, 2), 3)
如果我们想要试着用纯 python 实现 reduce(), 它可能会是这样:
if init is None: # initializer?
res = lseq.pop(0) #no
else:
res = init # yes
for item in lseq: # reduce sequence
res = bin_func(res, item) # apply function
return res
# return result
累加示例:
>>> def mySum(x,y): return x+y
>>> allNums = range(5) # [0, 1, 2, 3, 4]
>>> total = 0
>>> for eachNum in allNums:
... total = mySum(total, eachNum)
...
>>> print 'the total is:', total
the total is: 10
使用 lambda 和 reduce(),我们可以以一行代码做出相同的事情
>>> print 'the total is:', reduce((lambda x,y: x+y), range(5))
the total is: 10
给出了上面的输入,reduce()函数运行了如下的算术操作。
((((0 + 1) + 2) + 3) + 4) =>10
偏函数应用
标识符的作用域是定义为其声明在程序里的可应用范围, 或者即是我们所说的变量可见性。换句话说,就好像在问你自己,你可以在程序里的哪些部分去访问一个制定的标识符。变量可以是局部域或者全局域。
“声明适用的程序的范围被称为了声明的作用域。在一个过程中,如果名字在过程的声明之内,它的出现即为过程的局部变量;否则的话,出现即为非局部的“
全局变量的一个特征是除非被删除掉,否则它们的存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的
局部变量就像它们存放的栈,暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动。当一个函数调用出现时,其局部变量就进入声明它们的作用域。在那一刻,一个新的局部变量名为那个对象创建了,一旦函数完成,框架被释放,变量将会离开作用域。
python 的 lambda 匿名函数遵循和标准函数一样的作用域规则。一个 lambda 表达式定义了新的作用域,就像函数定义,所以这个作用域除了局部 lambda/函数,对于程序其他部分,该作用域都是不能对进行访问的。
那些声明为函数局部变量的 lambda 表达式在这个函数体内是可以访问的;然而,在 lambda 语句中的表达式有和函数相同的作用域。你也可以认为函数和一个 lambda 表达式是同胞。
x = 10
def foo():
y = 5
bar = lambda :x+y
print bar()
我们现在知道这段代码能很好的运行。
>>> foo()
15
修改y的值
x = 10
def foo():
y = 5
bar = lambda :x+y
print bar(y)
y = 8
print bar(y)
>>> foo()
15
18
当搜索一个标识符的时候,python 先从局部作用域开始搜索。如果在局部作用域内没有找到那个名字,那么就一定会在全局域(全局和内建的名字空间)找到这个变量否则就会被抛出 NameError 异常。
一个变量的作用域和它寄住的名字空间相关。子空间仅仅是将名字映射到对象的命名领域,现在使用的变量名字虚拟集合。作用域的概念和用于找到变量的名字空间搜索顺序相关。当一个函数执行的时候,所有在局部命名空间的名字都在局部作用域内。那就是当查找一个变量的时候,第一个被搜索的名字空间。如果没有在那找到变量的话,那么就可能找到同名的全局变量。这些变量存储(搜索)在一个全局以及内建的名字空间。
通过创建一个局部变量来“隐藏“或者覆盖一个全局变量是有可能的。回想一下,局部名字空间是首先被搜索的,存在于其局部作用域。如果找到一个名字,搜索就不会继续去寻找一个全局域的变量,所以在全局或者内建的名字空间内,可以覆盖任何匹配的名字。
如果将全局变量的名字声明在一个函数体内的时候,全局变量的名字能被局部变量给覆盖掉。
def foo():
print "\ncalling foo()..."
bar = 200
print "in foo(), bar is", bar
bar = 100
print "in __main__, bar is", bar
foo()
print "\nin __main__, bar is (still)", bar
得到如下输出:
in __main__, bar is 100
calling foo()...
in foo(), bar is 200
in __main__, bar is (still) 100
我们局部的 bar 将全局的 bar 推出了局部作用域。为了明确地引用一个已命名的全局变量,必须使用 global 语句。global 的语法如下:
global var1[, var2[, ... varN]]]
修改上面的例子,这样我们便可以用全局版本的 is_this_global 而无须创建一个新的局部变量。
>>>is_this_global = 'xyz'
>>>def foo():
... global is_this_global
... this_is_local = 'abc'
... is_this_global = 'def'
... print this_is_local + is_this_global
...
>>> foo()
abcdef
>>> print is_this_global
def
如果函数包含了对其自身的调用,该函数就是递归的。
递归广泛地应用于语言识别和使用递归函数的数学应用中。在本文的早先部分,我们第一次看到了我们定义的阶乘函数
N! ? factorial(N) ? 1 * 2 * 3 ... * N
我们可以用这种方式来看阶乘:
factorial(N) = N!
= N * (N-1)!
= N * (N-1) * (N-2)!
:
= N * (N-1) * (N-2) ... * 3 * 2 * 1
我们现在可以看到阶乘是递归的,因为 factorial(N) = N* factorial(N-1).换句话说,为了获得 factorial(N)的值,需要计算 factorial(N-1).而且,为了找到 factorial(N-1),需要计算factorial(N-2)等等。我们现在给出阶乘函数的递归版本。
def factorial(n):
if n == 0 or n == 1: # 0! = 1! = 1
return 1
else:
return (n * factorial(n-1))