函数进阶
函数的参数传递
Python的函数采用了引用传递的方法,即传递参数时,并不复制一份参数的内容,而是将参数的引用传递给函数。
例如:
def f(x):
    return id(x)a = 1.2id(a)4485901776f(a)4485901776函数f(a)的返回值与a的内存地址是一致的。这表示,当函数f()被调用时,Python并没有将a的值复制一份传给参数x,而是让参数x与a共享了同一块内存。所以a和x是同一个对象。
对于列表有类似的结论:
b = [1, 2, 3]f(b)4486278400id(b)4486278400共享同一个对象的机制意味着,可以在函数中修改传入参数的值。例如:
def mod_f(x):
    x[0] = 999
    return xb[1, 2, 3]mod_f(b)[999, 2, 3]b[999, 2, 3]不过,如果在函数中给参数x赋了一个新值,如另一个列表,根据赋值机制,虽然x指向一个新的内存位置,但原来的变量不会改变:
def no_mod_f(x):
    x = [4, 5, 6]
    return xno_mod_f(b)[4, 5, 6]b[999, 2, 3]默认参数的传递
有默认参数的情况下,Python会在函数定义时,预先为默认参数分配内存,以避免每次生成一个额外的默认参数。这样做能够节约一定的空间,不过也可能会得到一些与直觉不符的结果。例如:
def f(x = []):
    x.append(1)
    return x理论上说,调用 f() 时返回的是 [1], 但事实上:
f()[1]f()[1, 1]f()[1, 1, 1]随着函数的调用,默认参数会被一直改变。这并不是Python的bug,且这种特性可以用于缓存。如果不想要这样的特性,可以这样定义函数:
def f(x = None):
    if not x:
        x = []    
    x.append(1)
    return xf()[1]f()[1]高阶函数
以函数作为参数,或者返回一个函数的函数都是高阶函数。在Python中,函数也是一种基本类型的对象,例如:
maxtype(max)builtin_function_or_method对象性意味着可以对函数进行以下操作:
- 将函数作为参数传给另一个函数。
- 将函数名赋值给另一个变量。
- 将函数作为另一个函数的返回值。
例如:
def square(x):
    """Square of x."""
    return x*x
def cube(x):
    """Cube of x."""
    return x*x*x可以将它们作为字典的值:
funcs = {
    'square': square,
    'cube': cube,
}调用这些函数:
funcs['square'](3)9常见的高阶内置函数有 map() 和 filter()。
map(f, sq)将函数f作用在序列sq的每个元素上,得到结果组成的新序列。例如
map(square, range(5))返回的结果是一个map迭代器,可以将其转换为列表:
list(map(square, range(5)))[0, 1, 4, 9, 16]filter(f, sq)将函数f作用在序列sq的每个元素上,保留所有结果为True的元素。例如:
def is_even(x):
    return x % 2 == 0filter(is_even, range(5))list(filter(is_even, range(5)))[0, 2, 4]函数的返回值也可以是个函数。例如,定义一个返回函数的函数:
def power_func(num):
    def func(x):
        return x ** num
    return func平方和立方函数可以使用该函数定义出来:
square2 = power_func(2)cube2 = power_func(3)type(square2)functionsquare2(10)100cube2(3)27Lambda表达式
Python提供了Lambda表达式,简化函数的定义,来定义一些匿名函数。
lambda <variables>: <expression>Lambda表达式返回的是一个函数对象,接受<variables>作为参数,返回<expression>:
square3 = lambda x: x ** 2
cube3 = lambda x: x ** 3type(square3)functionsquare3(10)100cube3(3)27关键字global
在Python中,函数可以直接使用外部已定义好的变量值:
x = 15
def print_x():
    print(x)print_x()15如果想在函数中直接改x的值,需要加上关键字global:
x = 15
def print_newx():
    global x
    x = 18
    print(x)print_newx()18x18如果不加,x的值不会改变:
x = 15
def print_newx():
    x = 18
    print(x)print_newx()18x15递归
递归是指函数在执行的过程中调用了本身,一般用于分治法。例如,阶乘函数:
def fact(n):
    return 1 if n == 0 else n * fact(n-1)fact(6)720递归可以更快地实现代码,不过在效率上可能会有一定的损失。例如,斐波那契数列:
def fib1(n):
    """Fib with recursion."""
    return 1 if n in (0, 1) else fib1(n-1) + fib1(n-2)list(map(fib1, range(10)))[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]非递归的方式的实现:
def fib2(n):
    """Fib without recursion."""
    a, b = 1, 1
    for i in range(1, n+1):
        a, b = b, a+b
    return alist(map(fib2, range(10)))[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]对这两个函数的运行时间进行比较:
%timeit -n 100 fib1(20)2.98 ms ± 30.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)%timeit -n 100 fib2(20)1.5 µs ± 20.1 ns per loop (mean ± std. dev. of 7 runs, 100 loops each)可以看到,两者的效率有很大的差别,非递归版本比递归版本要快很多,原因是递归版本中存在大量的重复计算。
为了减少递归中的重复,可以利用默认参数实现缓存机制:
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]list(map(fib3, range(10)))[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]%timeit -n 100 fib3(20)158 ns ± 49.9 ns per loop (mean ± std. dev. of 7 runs, 100 loops each)