# 函数进阶:参数传递,高阶函数,lambda 匿名函数,global 变量,递归 ## 函数是基本类型 在 `Python` 中,函数是一种基本类型的对象,这意味着 * 可以将函数作为参数传给另一个函数 * 将函数作为字典的值储存 * 将函数作为另一个函数的返回值 In [1]: ```py def square(x): """Square of x.""" return x*x def cube(x): """Cube of x.""" return x*x*x ``` 作为字典的值: In [2]: ```py funcs = { 'square': square, 'cube': cube, } ``` 例子: In [3]: ```py x = 2 print square(x) print cube(x) for func in sorted(funcs): print func, funcs[func](x) ``` ```py 4 8 cube 8 square 4 ``` ## 函数参数 ### 引用传递 `Python` 中的函数传递方式是 `call by reference` 即引用传递,例如,对于这样的用法: ```py x = [10, 11, 12] f(x) ``` 传递给函数 `f` 的是一个指向 `x` 所包含内容的引用,如果我们修改了这个引用所指向内容的值(例如 `x[0]=999`),那么外面的 `x` 的值也会被改变。不过如果我们在函数中赋给 `x` 一个新的值(例如另一个列表),那么在函数外面的 `x` 的值不会改变: In [4]: ```py def mod_f(x): x[0] = 999 return x x = [1, 2, 3] print x print mod_f(x) print x ``` ```py [1, 2, 3] [999, 2, 3] [999, 2, 3] ``` In [5]: ```py def no_mod_f(x): x = [4, 5, 6] return x x = [1,2,3] print x print no_mod_f(x) print x ``` ```py [1, 2, 3] [4, 5, 6] [1, 2, 3] ``` ### 默认参数是可变的! 函数可以传递默认参数,默认参数的绑定发生在函数定义的时候,以后每次调用默认参数时都会使用同一个引用。 这样的机制会导致这种情况的发生: In [6]: ```py def f(x = []): x.append(1) return x ``` 理论上说,我们希望调用 `f()` 时返回的是 `[1]`, 但事实上: In [7]: ```py print f() print f() print f() print f(x = [9,9,9]) print f() print f() ``` ```py [1] [1, 1] [1, 1, 1] [9, 9, 9, 1] [1, 1, 1, 1] [1, 1, 1, 1, 1] ``` 而我们希望看到的应该是这样: In [8]: ```py def f(x = None): if x is None: x = [] x.append(1) return x print f() print f() print f() print f(x = [9,9,9]) print f() print f() ``` ```py [1] [1] [1] [9, 9, 9, 1] [1] [1] ``` ## 高阶函数 以函数作为参数,或者返回一个函数的函数是高阶函数,常用的例子有 `map` 和 `filter` 函数: `map(f, sq)` 函数将 `f` 作用到 `sq` 的每个元素上去,并返回结果组成的列表,相当于: ```py [f(s) for s in sq] ``` In [9]: ```py map(square, range(5)) ``` Out[9]: ```py [0, 1, 4, 9, 16] ``` `filter(f, sq)` 函数的作用相当于,对于 `sq` 的每个元素 `s`,返回所有 `f(s)` 为 `True` 的 `s` 组成的列表,相当于: ```py [s for s in sq if f(s)] ``` In [10]: ```py def is_even(x): return x % 2 == 0 filter(is_even, range(5)) ``` Out[10]: ```py [0, 2, 4] ``` 一起使用: In [11]: ```py map(square, filter(is_even, range(5))) ``` Out[11]: ```py [0, 4, 16] ``` `reduce(f, sq)` 函数接受一个二元操作函数 `f(x,y)`,并对于序列 `sq` 每次合并两个元素: In [12]: ```py def my_add(x, y): return x + y reduce(my_add, [1,2,3,4,5]) ``` Out[12]: ```py 15 ``` 传入加法函数,相当于对序列求和。 返回一个函数: In [13]: ```py def make_logger(target): def logger(data): with open(target, 'a') as f: f.write(data + '\n') return logger foo_logger = make_logger('foo.txt') foo_logger('Hello') foo_logger('World') ``` In [14]: ```py !cat foo.txt ``` ```py Hello World ``` In [15]: ```py import os os.remove('foo.txt') ``` ## 匿名函数 在使用 `map`, `filter`,`reduce` 等函数的时候,为了方便,对一些简单的函数,我们通常使用匿名函数的方式进行处理,其基本形式是: ```py lambda : ``` 例如,我们可以将这个: In [16]: ```py print map(square, range(5)) ``` ```py [0, 1, 4, 9, 16] ``` 用匿名函数替换为: In [17]: ```py print map(lambda x: x * x, range(5)) ``` ```py [0, 1, 4, 9, 16] ``` 匿名函数虽然写起来比较方便(省去了定义函数的烦恼),但是有时候会比较难于阅读: In [18]: ```py s1 = reduce(lambda x, y: x+y, map(lambda x: x**2, range(1,10))) print(s1) ``` ```py 285 ``` 当然,更简单地,我们可以写成这样: In [19]: ```py s2 = sum(x**2 for x in range(1, 10)) print s2 ``` ```py 285 ``` # global 变量 一般来说,函数中是可以直接使用全局变量的值的: In [20]: ```py x = 15 def print_x(): print x print_x() ``` ```py 15 ``` 但是要在函数中修改全局变量的值,需要加上 `global` 关键字: In [21]: ```py x = 15 def print_newx(): global x x = 18 print x print_newx() print x ``` ```py 18 18 ``` 如果不加上这句 `global` 那么全局变量的值不会改变: In [22]: ```py x = 15 def print_newx(): x = 18 print x print_newx() print x ``` ```py 18 15 ``` ## 递归 递归是指函数在执行的过程中调用了本身,一般用于分治法,不过在 `Python` 中这样的用法十分地小,所以一般不怎么使用: Fibocacci 数列: In [23]: ```py def fib1(n): """Fib with recursion.""" # base case if n==0 or n==1: return 1 # recurssive caae else: return fib1(n-1) + fib1(n-2) print [fib1(i) for i in range(10)] ``` ```py [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] ``` 一个更高效的非递归版本: In [24]: ```py def fib2(n): """Fib without recursion.""" a, b = 0, 1 for i in range(1, n+1): a, b = b, a+b return b print [fib2(i) for i in range(10)] ``` ```py [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] ``` 速度比较: In [25]: ```py %timeit fib1(20) %timeit fib2(20) ``` ```py 100 loops, best of 3: 5.35 ms per loop 100000 loops, best of 3: 2.2 µs per loop ``` 对于第一个递归函数来说,调用 `fib(n+2)` 的时候计算 `fib(n+1), fib(n)`,调用 `fib(n+1)` 的时候也计算了一次 `fib(n)`,这样造成了重复计算。 使用缓存机制的递归版本,这里利用了默认参数可变的性质,构造了一个缓存: In [26]: ```py def fib3(n, cache={0: 1, 1: 1}): """Fib with recursion and caching.""" try: return cache[n] except KeyError: cache[n] = fib3(n-1) + fib3(n-2) return cache[n] print [fib3(i) for i in range(10)] %timeit fib1(20) %timeit fib2(20) %timeit fib3(20) ``` ```py [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 100 loops, best of 3: 5.37 ms per loop 100000 loops, best of 3: 2.19 µs per loop The slowest run took 150.16 times longer than the fastest. This could mean that an intermediate result is being cached 1000000 loops, best of 3: 230 ns per loop ```