闭包
所谓闭包,简单的说就是在函数 f 里面定义另一个函数 g,然后把 g 作为 f 的返回值。由于 Python 里面函数是一等公民(可以看作对象,随意赋值),所以我们可以很方便地实现闭包:
def multiply(a):
def f(b):
return a * b
return f
l = (1, 2, 3, 4, 5)
# 输出(2, 4, 6, 8, 10)
print(tuple(map(multiply(2), l)))
# 输出(3, 6, 9, 12, 15)
print(tuple(map(multiply(3), l)))
关于闭包,有一个常见的坑,参见以下示例代码:
def funcGenerator(n):
l = []
for a in range(n):
def f(b):
return a * b
l.append(f)
return l
l = (1, 2, 3, 4, 5)
# 猜猜会输出什么?
print(tuple(map(funcGenerator(5)[2], l)))
你可能会觉得输出的是 (2, 4, 6, 8, 10),然而实际情况是输出 (4, 8, 12, 16, 20)!原因在于,Python 里面所有的“变量”本质上都只是对于对象的“引用”(或者说,类似于 C 中的指针),所以,最终所有的 a 都指向了数字 4。
正确的写法是这样的:
def multiply(a):
def f(b):
return a * b
return f
def funcGenerator(n):
l = []
for i in range(n):
l.append(multiply(i))
return l
一般说来,内层函数可以访问外层函数的变量,但是不能修改,为了解决这个问题,Python 引入了 nonlocal 关键字,只要加入一行声明“nonlocal <变量名>”,就可以修改外层函数的变量了。
小问题:下面的代码会输出什么?(答案在文末)
def multiply(a):
def f(b):
nonlocal a
a += 1
return a * b
return f
l = (1, 2, 3, 4, 5)
f1 = multiply(2)
f2 = multiply(2)
print(tuple(map(f1, l)))
print(tuple(map(f1, l)))
print(tuple(map(f2, l)))
函数装饰器
装饰器就是 Python 的一个语法糖,只要理解了闭包,装饰器也就不在话下了。
假设我们需要测量一个函数的执行时间,最简单的办法就是利用 time 标准库中的 perf_counter()。
import time
def f():
pass
a = time.perf_counter()
f()
print(time.perf_counter() - a)
但是很多时候,我们只是需要临时测量一下函数的执行时间,有没有什么办法,可以简单地完成这个任务,同时,测量完之后,又可以很快地收拾干净呢?装饰器可以解决这个问题。我们先编写一个装饰器:
import functools
def timing(func):
# 这一行的作用比较复杂,只要记住一定要加上就好
@functools.wraps(func)
def wrapper(*args, **kw):
a = time.perf_counter()
r = func(*args, **kw)
print(time.perf_counter() - a)
return r
return wrapper
这里的 timing(func) 就是一个典型的装饰器,要使用它,只需要在函数定义时,加上一行 @timing 即可。
# 相当于:f = timing(f)
@timing
def f():
pass
f()
这样,f 就被我们“装饰”了,执行 f 的时候,功能不变,只不过结束时,会打印出执行的时间。想要恢复正常时,把 @timing 去掉即可。
我们再看一个更复杂的例子:
def replace(new_func):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
return new_func(*args, **kw)
return wrapper
return decorator
@repalce(lambda *args, **kw: None)
def f():
print('foobar')
f()
这里出现了三重嵌套。replace 是一个函数,它返回一个装饰器。然后我们用这个装饰器修饰 f,实现了“偷梁换柱”,用 lambda *args, **kw: None 这个匿名函数替换了原先的 f。当然,你也可以玩一个四重甚至 n 重嵌套,不过很少用。
答案:
(3, 8, 15, 24, 35)
(8, 18, 30, 44, 60)
(3, 8, 15, 24, 35)