#!/usr/bin/env python # coding: utf-8 # # 函数进阶 # # ## 函数的参数传递 # Python的函数采用了引用传递的方法,即传递参数时,并不复制一份参数的内容,而是将参数的引用传递给函数。 # # 例如: # In[1]: def f(x): return id(x) # In[2]: a = 1.2 # In[3]: id(a) # In[4]: f(a) # 函数`f(a)`的返回值与a的内存地址是一致的。这表示,当函数`f()`被调用时,Python并没有将a的值复制一份传给参数x,而是让参数x与a共享了同一块内存。所以a和x是同一个对象。 # # 对于列表有类似的结论: # In[5]: b = [1, 2, 3] # In[6]: f(b) # In[7]: id(b) # 共享同一个对象的机制意味着,可以在函数中修改传入参数的值。例如: # In[8]: def mod_f(x): x[0] = 999 return x # In[9]: b # In[10]: mod_f(b) # In[11]: b # 不过,如果在函数中给参数x赋了一个新值,如另一个列表,根据赋值机制,虽然x指向一个新的内存位置,但原来的变量不会改变: # In[12]: def no_mod_f(x): x = [4, 5, 6] return x # In[13]: no_mod_f(b) # In[14]: b # ## 默认参数的传递 # 有默认参数的情况下,Python会在函数定义时,预先为默认参数分配内存,以避免每次生成一个额外的默认参数。这样做能够节约一定的空间,不过也可能会得到一些与直觉不符的结果。例如: # In[15]: def f(x = []): x.append(1) return x # 理论上说,调用 `f()` 时返回的是 `[1]`, 但事实上: # In[16]: f() # In[17]: f() # In[18]: f() # 随着函数的调用,默认参数会被一直改变。这并不是Python的bug,且这种特性可以用于缓存。如果不想要这样的特性,可以这样定义函数: # In[19]: def f(x = None): if not x: x = [] x.append(1) return x # In[20]: f() # In[21]: f() # ## 高阶函数 # 以函数作为参数,或者返回一个函数的函数都是高阶函数。在Python中,函数也是一种基本类型的对象,例如: # In[22]: max # In[23]: type(max) # 对象性意味着可以对函数进行以下操作: # - 将函数作为参数传给另一个函数。 # - 将函数名赋值给另一个变量。 # - 将函数作为另一个函数的返回值。 # # 例如: # In[24]: def square(x): """Square of x.""" return x*x def cube(x): """Cube of x.""" return x*x*x # 可以将它们作为字典的值: # In[25]: funcs = { 'square': square, 'cube': cube, } # 调用这些函数: # In[26]: funcs['square'](3) # 常见的高阶内置函数有 `map()` 和 `filter()`。 # ```python # map(f, sq) # ``` # 将函数f作用在序列sq的每个元素上,得到结果组成的新序列。例如 # In[27]: map(square, range(5)) # 返回的结果是一个map迭代器,可以将其转换为列表: # In[28]: list(map(square, range(5))) # ```python # filter(f, sq) # ``` # 将函数f作用在序列sq的每个元素上,保留所有结果为`True`的元素。例如: # In[29]: def is_even(x): return x % 2 == 0 # In[30]: filter(is_even, range(5)) # In[31]: list(filter(is_even, range(5))) # 函数的返回值也可以是个函数。例如,定义一个返回函数的函数: # In[32]: def power_func(num): def func(x): return x ** num return func # 平方和立方函数可以使用该函数定义出来: # In[33]: square2 = power_func(2) # In[34]: cube2 = power_func(3) # In[35]: type(square2) # In[36]: square2(10) # In[37]: cube2(3) # ## Lambda表达式 # # Python提供了Lambda表达式,简化函数的定义,来定义一些匿名函数。 # # ```python # lambda : # ``` # # Lambda表达式返回的是一个函数对象,接受``作为参数,返回``: # In[38]: square3 = lambda x: x ** 2 cube3 = lambda x: x ** 3 # In[39]: type(square3) # In[40]: square3(10) # In[41]: cube3(3) # ## 关键字global # # 在Python中,函数可以直接使用外部已定义好的变量值: # In[42]: x = 15 def print_x(): print(x) # In[43]: print_x() # 如果想在函数中直接改x的值,需要加上关键字`global`: # In[44]: x = 15 def print_newx(): global x x = 18 print(x) # In[45]: print_newx() # In[46]: x # 如果不加,x的值不会改变: # In[47]: x = 15 def print_newx(): x = 18 print(x) # In[48]: print_newx() # In[49]: x # ## 递归 # # 递归是指函数在执行的过程中调用了本身,一般用于分治法。例如,阶乘函数: # In[50]: def fact(n): return 1 if n == 0 else n * fact(n-1) # In[51]: fact(6) # 递归可以更快地实现代码,不过在效率上可能会有一定的损失。例如,斐波那契数列: # In[52]: def fib1(n): """Fib with recursion.""" return 1 if n in (0, 1) else fib1(n-1) + fib1(n-2) # In[53]: list(map(fib1, range(10))) # 非递归的方式的实现: # In[54]: def fib2(n): """Fib without recursion.""" a, b = 1, 1 for i in range(1, n+1): a, b = b, a+b return a # In[55]: list(map(fib2, range(10))) # 对这两个函数的运行时间进行比较: # In[56]: get_ipython().run_line_magic('timeit', '-n 100 fib1(20)') # In[57]: get_ipython().run_line_magic('timeit', '-n 100 fib2(20)') # 可以看到,两者的效率有很大的差别,非递归版本比递归版本要快很多,原因是递归版本中存在大量的重复计算。 # # 为了减少递归中的重复,可以利用默认参数实现缓存机制: # In[58]: 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] # In[59]: list(map(fib3, range(10))) # In[60]: get_ipython().run_line_magic('timeit', '-n 100 fib3(20)') # In[ ]: