函数进阶:参数传递,高阶函数,lambda 匿名函数,global 变量,递归

函数是基本类型

Python 中,函数是一种基本类型的对象,这意味着

  • 可以将函数作为参数传给另一个函数
  • 将函数作为字典的值储存
  • 将函数作为另一个函数的返回值

In [1]:

  1. def square(x):
  2. """Square of x."""
  3. return x*x
  4.  
  5. def cube(x):
  6. """Cube of x."""
  7. return x*x*x

作为字典的值:

In [2]:

  1. funcs = {
  2. 'square': square,
  3. 'cube': cube,
  4. }

例子:

In [3]:

  1. x = 2
  2.  
  3. print square(x)
  4. print cube(x)
  5.  
  6. for func in sorted(funcs):
  7. print func, funcs[func](x)
  1. 4
  2. 8
  3. cube 8
  4. square 4

函数参数

引用传递

Python 中的函数传递方式是 call by reference 即引用传递,例如,对于这样的用法:

  1. x = [10, 11, 12]
  2. f(x)

传递给函数 f 的是一个指向 x 所包含内容的引用,如果我们修改了这个引用所指向内容的值(例如 x[0]=999),那么外面的 x 的值也会被改变。不过如果我们在函数中赋给 x 一个新的值(例如另一个列表),那么在函数外面的 x 的值不会改变:

In [4]:

  1. def mod_f(x):
  2. x[0] = 999
  3. return x
  4.  
  5. x = [1, 2, 3]
  6.  
  7. print x
  8. print mod_f(x)
  9. print x
  1. [1, 2, 3]
  2. [999, 2, 3]
  3. [999, 2, 3]

In [5]:

  1. def no_mod_f(x):
  2. x = [4, 5, 6]
  3. return x
  4.  
  5. x = [1,2,3]
  6.  
  7. print x
  8. print no_mod_f(x)
  9. print x
  1. [1, 2, 3]
  2. [4, 5, 6]
  3. [1, 2, 3]

默认参数是可变的!

函数可以传递默认参数,默认参数的绑定发生在函数定义的时候,以后每次调用默认参数时都会使用同一个引用。

这样的机制会导致这种情况的发生:

In [6]:

  1. def f(x = []):
  2. x.append(1)
  3. return x

理论上说,我们希望调用 f() 时返回的是 [1], 但事实上:

In [7]:

  1. print f()
  2. print f()
  3. print f()
  4. print f(x = [9,9,9])
  5. print f()
  6. print f()
  1. [1]
  2. [1, 1]
  3. [1, 1, 1]
  4. [9, 9, 9, 1]
  5. [1, 1, 1, 1]
  6. [1, 1, 1, 1, 1]

而我们希望看到的应该是这样:

In [8]:

  1. def f(x = None):
  2. if x is None:
  3. x = []
  4. x.append(1)
  5. return x
  6.  
  7. print f()
  8. print f()
  9. print f()
  10. print f(x = [9,9,9])
  11. print f()
  12. print f()
  1. [1]
  2. [1]
  3. [1]
  4. [9, 9, 9, 1]
  5. [1]
  6. [1]

高阶函数

以函数作为参数,或者返回一个函数的函数是高阶函数,常用的例子有 mapfilter 函数:

map(f, sq) 函数将 f 作用到 sq 的每个元素上去,并返回结果组成的列表,相当于:

  1. [f(s) for s in sq]

In [9]:

  1. map(square, range(5))

Out[9]:

  1. [0, 1, 4, 9, 16]

filter(f, sq) 函数的作用相当于,对于 sq 的每个元素 s,返回所有 f(s)Trues 组成的列表,相当于:

  1. [s for s in sq if f(s)]

In [10]:

  1. def is_even(x):
  2. return x % 2 == 0
  3.  
  4. filter(is_even, range(5))

Out[10]:

  1. [0, 2, 4]

一起使用:

In [11]:

  1. map(square, filter(is_even, range(5)))

Out[11]:

  1. [0, 4, 16]

reduce(f, sq) 函数接受一个二元操作函数 f(x,y),并对于序列 sq 每次合并两个元素:

In [12]:

  1. def my_add(x, y):
  2. return x + y
  3.  
  4. reduce(my_add, [1,2,3,4,5])

Out[12]:

  1. 15

传入加法函数,相当于对序列求和。

返回一个函数:

In [13]:

  1. def make_logger(target):
  2. def logger(data):
  3. with open(target, 'a') as f:
  4. f.write(data + '\n')
  5. return logger
  6.  
  7. foo_logger = make_logger('foo.txt')
  8. foo_logger('Hello')
  9. foo_logger('World')

In [14]:

  1. !cat foo.txt
  1. Hello
  2. World

In [15]:

  1. import os
  2. os.remove('foo.txt')

匿名函数

在使用 mapfilterreduce 等函数的时候,为了方便,对一些简单的函数,我们通常使用匿名函数的方式进行处理,其基本形式是:

  1. lambda <variables>: <expression>

例如,我们可以将这个:

In [16]:

  1. print map(square, range(5))
  1. [0, 1, 4, 9, 16]

用匿名函数替换为:

In [17]:

  1. print map(lambda x: x * x, range(5))
  1. [0, 1, 4, 9, 16]

匿名函数虽然写起来比较方便(省去了定义函数的烦恼),但是有时候会比较难于阅读:

In [18]:

  1. s1 = reduce(lambda x, y: x+y, map(lambda x: x**2, range(1,10)))
  2. print(s1)
  1. 285

当然,更简单地,我们可以写成这样:

In [19]:

  1. s2 = sum(x**2 for x in range(1, 10))
  2. print s2
  1. 285

global 变量

一般来说,函数中是可以直接使用全局变量的值的:

In [20]:

  1. x = 15
  2.  
  3. def print_x():
  4. print x
  5.  
  6. print_x()
  1. 15

但是要在函数中修改全局变量的值,需要加上 global 关键字:

In [21]:

  1. x = 15
  2.  
  3. def print_newx():
  4. global x
  5. x = 18
  6. print x
  7.  
  8. print_newx()
  9.  
  10. print x
  1. 18
  2. 18

如果不加上这句 global 那么全局变量的值不会改变:

In [22]:

  1. x = 15
  2.  
  3. def print_newx():
  4. x = 18
  5. print x
  6.  
  7. print_newx()
  8.  
  9. print x
  1. 18
  2. 15

递归

递归是指函数在执行的过程中调用了本身,一般用于分治法,不过在 Python 中这样的用法十分地小,所以一般不怎么使用:

Fibocacci 数列:

In [23]:

  1. def fib1(n):
  2. """Fib with recursion."""
  3.  
  4. # base case
  5. if n==0 or n==1:
  6. return 1
  7. # recurssive caae
  8. else:
  9. return fib1(n-1) + fib1(n-2)
  10.  
  11. print [fib1(i) for i in range(10)]
  1. [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

一个更高效的非递归版本:

In [24]:

  1. def fib2(n):
  2. """Fib without recursion."""
  3. a, b = 0, 1
  4. for i in range(1, n+1):
  5. a, b = b, a+b
  6. return b
  7.  
  8. print [fib2(i) for i in range(10)]
  1. [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

速度比较:

In [25]:

  1. %timeit fib1(20)
  2. %timeit fib2(20)
  1. 100 loops, best of 3: 5.35 ms per loop
  2. 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]:

  1. def fib3(n, cache={0: 1, 1: 1}):
  2. """Fib with recursion and caching."""
  3.  
  4. try:
  5. return cache[n]
  6. except KeyError:
  7. cache[n] = fib3(n-1) + fib3(n-2)
  8. return cache[n]
  9.  
  10. print [fib3(i) for i in range(10)]
  11.  
  12. %timeit fib1(20)
  13. %timeit fib2(20)
  14. %timeit fib3(20)
  1. [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
  2. 100 loops, best of 3: 5.37 ms per loop
  3. 100000 loops, best of 3: 2.19 µs per loop
  4. The slowest run took 150.16 times longer than the fastest. This could mean that an intermediate result is being cached
  5. 1000000 loops, best of 3: 230 ns per loop

原文: https://nbviewer.jupyter.org/github/lijin-THU/notes-python/blob/master/05-advanced-python/05.08-functions.ipynb