上一篇中介绍了 Python 中函数的定义、函数的调用、函数的参数以及变量的作用域等内容,现在来说下函数的一些高级特性:
函数是可以被调用的,且一个函数内部可以调用其他函数。如果一个函数在内部调用本身,这个函数就是一个递归函数。函数递归调用的过程与循环相似,而且理论上,所有的递归函数都可以写成循环的方式,但是递归函数的优点是定义简单,逻辑清晰。递归和循环都是一个重复的操作的过程,这些重复性的操作必然是需要有一定的规律性的。另外,很明显递归函数也需要一个结束条件,否则就会像死循环一样递归下去,直到由于栈溢出而被终止(这个下面介绍)。
可见,要实现一个递归函数需要确定两个要素:
思路有两个:
- def fact(n):
- if n == 0:
- return 1
- result = 1
- while n >= 1:
- result *= n
- n -= 1
- return result
先来确定递归函数的两个要素:
- def fact(n):
- if n == 0:
- return 1
- return fact(n-1) * n
怎么样?递归函数的实现方式是不是既简单、又清晰。
我们计算 fact(5) 的计算过程是这样的:
- ===> fact(5)
- ===> 5 * fact(4)
- ===> 5 * (4 * fact(3))
- ===> 5 * (4 * (3 * fact(2)))
- ===> 5 * (4 * (3 * (2 * fact(1))))
- ===> 5 * (4 * (3 * (2 * 1)))
- ===> 5 * (4 * (3 * 2))
- ===> 5 * (4 * 6)
- ===> 5 * 24
- ===> 120
同理,要实现求 1 + 2 + 3 + ... + n,可以这样写:
- def fact(n):
- if n == 1:
- return 1
- return fact(n-1) + n
定义简单、逻辑清晰。
效率并不高且需要注意防止栈溢出。
大家会发现上面实现的递归函数在运算的过程中 n 是逐渐减小的,也就是说问题规模应该是逐层减少的。
下面我们来总结写递归的特性:
每种编程语言都对递归函数可递归的深度有限制(),有些是跟相应内存空间的分配有关(因为栈是在内存空间中的),如 Java。Python 中对递归的深度限制默认为 1000,可以通过
函数来获取该值,超过这个深度会报错:
- sys.getrecursionlimit()
。当然也可以通过
- RecursionError: maximum recursion depth exceeded in comparison
来设置新的限制值。
- sys.setrecursionlimit(n)
嵌套函数是指在函数内部定义一个函数,这些函数都遵循各自的作用域和生命周期规则。
来看个例子:
- def outer():
- level = 1
- print('outer', level)
- def inner():
- print('inner', level)
- inner()
调用 outer 函数
,输出结果如下:
- outer()
- outer 1
- inner 1
再来看个例子:
- def outer():
- level = 1
- print('outer', level)
- def inner():
- level = 2
- print('inner', level)
- inner()
调用 outer 函数
,输出结果如下:
- outer()
- outer 1
- inner 2
嵌套函数查找变量的顺序是:先查找自己函数体内部是否包含该变量,如果包含则直接应用,如果不包含则查找外层函数体内是否包含该函数,依次向外。
首先要说明一个问题:,我们通过 def 定义一个函数时,实际上就是在定义一个变量,函数名就是变量名称,函数体就是该变量的值。我们知道,变量是可以赋值给其他变量的,因此函数也是可以被当做返回值返回的,并且可以赋值给其他变量。
- def outer(x):
- def inner(y):
- print(x+y)
- return inner
- f1 = outer(10)
- f2 = outer(20)
- f1(100)
- f2(100)
上面操作的执行结果是:
- 110
- 120
我们知道局部变量的作用域是在定义它的函数体内部,局部变量在函数执行时进行声明,函数执行完毕则会被释放。上面也提到过了,函数也是一个变量,那么嵌套函数内部定义的函数也是一个局部变量,也就是说嵌套函数每调用一次,其内部的函数都会被定义一次。因此,在上面的示例中
- f1 = outer(10)
- f2 = outer(20)
对于 f1 和 f2 而言,两次调用嵌套函数 outer 并返回的内部函数 inner 是不同的,且它们取到的 x 值也是不同的。从表面上来看 f1 和 f2 相当于把 x 分别替换成了 10 和 20:
- def f1(y):
- print(10+y)
- def f2(y):
- print(20+y)
但实际上不是这样的,f1 和 f2 还是这样的:
- def f1(y):
- print(x+y)
- def f2(y):
- print(x+y)
f1 和 f2 被调用时,y 的值是通过参数传递进来的(100),而 x 还是个变量。inner 函数会在自己的函数体内部查找该局部变量 x,发现没找到,然后去查找它外层的函数局部变量 x,找到了。这里好像出现问题了,因为之前说过了局部变量会在函数执行结束后被释放,那么 f1 和 f2 被调用时 outer 函数已经执行完了,理论上 x 的值应该被释放了才对啊,为什么还能引用 x 的值?其实,这就是闭包的作用。
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数应用了外部函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组 "私有" 变量之间创建关联关系。在该内部函数被多次调用的过程中,这些私有变量能够保持其持久性。在支持将函数作为对象使用的编程语言中,一般都支持闭包,比如:Python、PHP、Javascript 等。
闭包就是根据不同的配置信息得到不同的结果。专业解释是:闭包(closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的应用环境组合而成的实体。
Ptyhon 支持一种特性叫做函数闭包(function closres),它的工作原理是:在非全局(global)作用域(函数)中定义 inner 函数时,这个 inner 函数会记录下外层函数的 namespaces(外层函数作用域的 locals,其中包括外层函数局部作用域中的所有变量),可以称作:定义时状态,inner 函数可以通过__closure__(早期版本中为 func_closure)这个属性来获得 inner 函数外层嵌套函数的 namespaces。其实我们可以通过打印一个函数的__closesure__属性值是否为 None 来判断闭包是否发生。
其实装饰器就是一种闭包,或者说装饰器是闭包的一种经典应用。区别在于,装饰器的参数(配置信息)是一个函数或类,专门对类或函数进行加工、处理和功能增强。关于装饰器,我们会在后面详细介绍。
在 Python 中有两种定义函数的方式:
lambda 作为一个关键字,作为引入表达式的语法。与 def 定义的函数相比较而言,lambda 是单一的表达式,而不是语句块。也就是说,我们仅仅能够在 lambda 中封装有限的业务逻辑(通常只是一个表达式),这样设计的目的在于:让 lambda 纯粹为了编写简单的函数 (通常称为小函数) 而设计,def 则专注于处理更大的业务。
- lambda argument1,
- argument2,
- ...argumentN: expression using argments
冒号左边是函数的参数,冒号右边是一个整合参数并计算返回值的表达式。
def 函数
- def add(x, y):
- return x + y
lambda 函数
- lambda x,
- y: x + y
调用方式 1:匿名函数也是一个函数对象,可以将匿名函数赋值给一个变量,然后通过在这个变量后加上一对小括号来调用:
- add = lambda x, y: x+y
- sum = add(1, 2)
调用方式 2:直接在 lambda 函数后加上一堆小括号调用:
- sum = (lambda x, y: x+y)(1, 3)
从上面提到的 "匿名函数的调用方式" 来看,匿名函数貌似没有什么卵用,反而可读性更差了。那么匿名函数在 Python 中存在的意义是什么呢?匿名函数一般应用于函数式编程中,在 Python 中通常是指与高阶函数的配合使用 -- 把匿名函数当做高阶函数的参数来使用,下面的高阶函数实例中会用到。
我们上面已经提到过:函数名也是变量,函数名就是指向函数的变量。并且我们已经知道:变量是可以作为参数传递给函数的。由此,我们得出一个结论:函数是一个接受另外一个函数作为参数的,而这种函数就称为高阶函数(Higher-order function)。
我们来自定义一个高阶函数,这个函数用于求两个数的和,同时接收一个函数用于在求和之前对两个数值参数做一些额外的处理(如:取绝对值、求平方或其他任意操作)
- def nb_add(x, y, f):
- return f(x) + f(y)
其中 x,y 是用于求和的两个数值参数,f 是对 x,y 进行处理的函数。我们试着先给 f 传递一个内置的 abs(取绝对值) 函数,也就是说先对 x 和 y 分别取绝对值,然后再相加:
- result = nb_add(10, -20, abs)
- print(result)
运行结果是:
- 30
我们来自定义一个求平方的方法,然后传递给 f 试试:
- def pow2(x):
- return pow(x, 2)
- result = nb_add(10, -20, pow2)
- print(result)
输出结果是:
- 500
我们发现上面定义的 pow2(x) 函数的函数体只有一个表达式,因此我们完全可以不单独定义该函数而使用匿名函数来实现,这样可以使 diamante 变得更简洁:
- def nb_add(x, y, f):
- return f(x) + f(y)
- result = nb_add(10, 20, lambda x: pow(x, 2))
- print(result)
Python 内置了一些非常有用的高阶函数,下面我们来看看常见的几个:
- map(function, iterable, ...)
将传入的函数依次作用到可迭代对象的每个元素,并把结果作为新的迭代器对象(Iterator)返回(Python2.x 中会直接返回一个列表)。
- def pow2(x):
- return x * x
- L = [1, 2, 3, 4, 5, 6]
- list1 = list(map(pow2, L))
- print(list1)
输出结果为:
- [1, 4, 9, 16, 25, 36]
上面已经演示过,pow2() 可以直接使用匿名函数:
- L = [1, 2, 3, 4, 5, 6]
- list1 = list(map(lambda x: pow(x, 2), L))
- print(list1)
可见 map 函数作为高阶函数,事实上是把运算规则抽象了,因此,我们不仅可以计算简单的 f(x)=x*x,还可以计算任意复杂的函数。
- L = [1, 2, 3, 4, 5, 6]
- T = (7, 8, 9, 10)
- list1 = list(map(lambda x, y: x+y, L, T))
- print(list1)
输出结果为:
- [8, 10, 12, 14]
- reduce(function, sequence, initializer=None)
把一个函数作用在指定的序列上,这个函数必须接收两个参数,然后把计算结果继续和序列的下一个元素做累计计算,最终返回一个结果。简单来讲,就是对一个序列中的元素做聚合运算。
- from functools import reduce
- L = [1, 2, 3, 4, 5]
- sum1 = reduce(lambda x, y: x + y, L)
- print(sum1)
- sum2 = reduce(lambda x, y: x + y, L, 6)
- print(sum2)
输出结果为:
- 15
- 21
这个过程相当于:
- (((1 + 2) + 3) + 4) + 5
- from functools import reduce
- def fn(x, y):
- return int(x)*10 + int(y)
- num = reduce(lambda x, y: int(x)*10 + y, '12345')
- print(num)
也可以封装成一个函数:
- from functools import reduce
- def str2int(s):
- return reduce(lambda x, y: int(x)*10 + int(y), s)
- num = str2int('12345')
- print(num)
也可以先通过 map 函数将字符串中的字符转成 int,然后再通过 reduce 进行运算:
- from functools import reduce def str2int(s) : def char2num(c) : return {
- '0': 0,
- '1': 1,
- '2': 2,
- '3': 3,
- '4': 4,
- '5': 5,
- '6': 6,
- '7': 7,
- '8': 8,
- '9': 9
- } [c]
- return reduce(lambda x, y: x * 10 + y, map(char2num, s)) num = str2int('12345') print(num)
其实 char2sum 也可以用匿名函数来实现,但是可读性不太好。另外我举这个例子的本义不是为了单纯的演示 map/reduce / 匿名函数的使用,而是想说明嵌套函数与高阶函数综合使用的场景,这在某些场景下可以使代码逻辑变得更清晰。
- filter(function, iterable)
用于过滤可迭代对象,具体过程是:把传入的函数依次作用于可迭代对象的每个元素,如果函数返回值为 Ture 则保留该元素,如果返回值为 False 则丢弃该元素,并最终把保留的元素作为一个 iterator(迭代器)返回。如果 function 是 None,则根据可迭代对象各元素的真值测试结果决定是否保留该元素。
- from itertools import filterfalse
- L = [1, 2, 3, 4, 5, 6, 7, 8, 9]
- odd_num = list(filter(lambda x: x % 2 == 1, L))
- even_num = list(filterfalse(lambda x: x%2 == 1, L))
- print('奇数:', odd_num)
- print('偶数:', even_num)
输出结果:
- 奇数: [1, 3, 5, 7, 9]
- 偶数: [2, 4, 6, 8]
- L = ['ABC', '', 'DEF', ' ', '1233', None]
- list1 = list(filter(None, L))
- print(list1)
输出结果为:
- ['ABC', 'DEF', ' ', '1233']
由于第 4 个由 3 个空白字符组成的字符串的真值测试结果为 True,因此它还是会被保留。被看来还是需要传递个函数参数才行:
- L = ['ABC', '', 'DEF', ' ', '1233', None]
- list1 = list(filter(lambda s: s and s.strip(), L))
- print(list1)
输出结果:
- ['ABC', 'DEF', '1233']
- sorted(iterable[, key][, reverse])
对可迭代对象 iterable 中的元素进行排序,并将排序结果作为一个新的 list 返回。
- list1 = sorted([10, 9, -21, 13, -30])
- list2 = sorted([10, 9, -21, 13, -30], key=abs)
- list3 = sorted([10, 9, -21, 13, -30], key=abs, reverse=True)
- print(list1)
- print(list2)
- print(list3)
输出结果:
- [-30, -21, 9, 10, 13]
- [9, 10, 13, -21, -30]
- [-30, -21, 13, 10, 9]
- list1 = sorted(['how', 'What', 'check', 'Zero'])
- list2 = sorted(['how', 'What', 'check', 'Zero'], key=lower)
- list3 = sorted(['how', 'What', 'check', 'Zero'], key=lower, reverse=True)
- print(list1)
- print(list2)
- print(list3)
输出结果:
- ['What', 'Zero', 'check', 'how']
- ['check', 'how', 'What', 'Zero']
- ['Zero', 'What', 'how', 'check']
假设我们用一组 tuple 表示姓名和年龄,然后用 sorted() 函数分别按姓名升序和年龄降序进行排序:
- def sort_by_name(t):
- return t[0]
- def sort_by_age(t):
- return t[1]
- L = [('Tom', 18), ('Jerry', 15), ('Peter', 16), ('John', 20)]
- list1 = sorted(L, key=sort_by_name)
- list2 = sorted(L, key=sort_by_age, reverse=True)
- print('sort by name asc: ', list1)
- print('sort by age desc: ', list2)
输出结果:
- sort by name asc: [('Jerry', 15), ('John', 20), ('Peter', 16), ('Tom', 18)]
- sort by age desc: [('John', 20), ('Tom', 18), ('Peter', 16), ('Jerry', 15)]
对字典排序的方法有很多中,但核心思想都是一样的:把 dict 中的 key 或 value 或 item 分离出来放到一个 list 中,然后在对这个 list 进行排序,从而间接实现对 dict 的排序。
- D = {
- 'Tom': 18,
- 'Jerry': 15,
- 'Peter': 16,
- 'John': 20
- }
- list1 = sorted(D.items(), key = lambda d: d[0]) list2 = sorted(D.items(), key = lambda d: d[1], reverse = True) print('sort by key asc:', list1) print('sort by value desc:', list2)
输出结果:
- sort by key asc: [('Jerry', 15), ('John', 20), ('Peter', 16), ('Tom', 18)]
- sort by value desc: [('John', 20), ('Tom', 18), ('Peter', 16), ('Jerry', 15)]
Python 解释器有许多内置的函数和类型,有一些之前已经用到过,比如:
这些函数我们在之前的文章中基本都演示了,不在此赘述。关于他们的详细说明以及其它内置函数的使用可以参考下面给出的列表及官方文档连接地址。
Python 3 相对于 Python 2 的内置函数有些变动:
这里讲了分别讲了 Python 中函数的一些高级应用,如果能把这些内容整合起来灵活运用会发挥很大的威力。比如后面要说到的装饰就是高阶函数、嵌套函数以及闭包的一个典型应用。
来源: