Z.S.K.'s Records

python闭包及装饰器

代码调试结果再一次被自己已有的认知打败,恶补基础,今天扯一扯python的闭包及装饰器.

有意思的代码

1
2
3
4
def f():
return [lambda x:i*x for i in range(4)]

print([m(2) for m in f()])

大家很快能得到答案: [0,2,4,6],但是这真的对吗?

正确答案为[6,6,6,6],我也一脸蒙蔽: But How?

首先可以肯定的是函数f返回一个列表解析,列表里的对象是4个lambda对象

当使用f()调用f函数时,f返回了一个列表,列表里也确实是4个lambda对象,这个问题的关键在于i的值,对于lambda来说,变量i的值 永远都是for循环里最后一次i的值,也就是3,4个lambda都是如此

再看这段代码

1
2
3
4
5
6
7
8
9
10
11
data = range(4)

funcs = []
for i in data:
l = lambda x: i * x
funcs.append(l)

i = 21
for func in funcs:
print(func(2))
#输出:42,42,42,42

Python的闭包(lambda和function等)会保存变量的名字(i)和scoping(global)。当你调用的时候才会去找具体的对象。所以你给i赋不同的值,在那之后的调用都会去取新的i = 21,for i in data中的i值显然不是最新值.

python闭包

python中的闭包从表现形式上定义为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)

1
2
3
4
5
6
7
8
9
10
11
12
def func1(args):
def inner_func(inner_args):
return (args + inner_args)
return inner_func

#在func1中嵌套了另一函数inner_func,而在inner_func中引用了args
aa = func1(10) #此时 args = 10
print aa
#输出 <function func1.<locals>.inner_func at 0x00000000038DC400>
#从这里可以看出 aa = func1(10),aa指向了函数inner_func
print(aa(20)) #此时 inner_args = 20
#输出 30

inner_func中使用了args,但是inner_func中并没有定义args,此时python解析器会向上查找,在func1找到使用.

全局作用域无法访问局部作用域,而局部作用域能够访问全局作用域就这这个原因。而当我在局部作用域创建了一个和外面同名的变量时,python在找这个变量的时候首先会在当前作用域中找,找到了,就不继续往上一级找了

当函数存在嵌套,并且子函数引用了父函数中的变量,可以访问这些变量的作用域就形成闭包,如果子函数没有访问父函数中的变量,就不存在闭包

内部函数inner_func引用了外部func1的变量,所有inner_func是闭包.

那第一段代码如何才能得到结果为[0,2,4,6]呢,可以这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#方法一:
def f():
return [lambda x,i = i:i*x for i in range(4)]
#添加一个i = i,这样就能够直接使用for i 局部变更的 i 值

print([m(2) for m in f()])
#输出
[0,2,4,6]

#方法二:
def f():
return (lambda x:i*x for i in range(4))
#把[] 换成 (),变成一个生成器,生成器则是先生一个值,应用于lambda后再生成一个值,不会像列表解析一样一次性生成

print([m(2) for m in f()])
#输出
[0,2,4,6]

闭包的最常用的使用场景即是装饰器

python装饰器

因为python中一切皆对象,函数也是对象,所以也可以将函数做为参数传递,我们将上面的例子改一改,将函数做为参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def func1(func):
def inner_func():
print("开始执行前")
func()
print("执行结束")
return inner_func

def func2():
print("我执行了func2")

a = func1(func2)
#这里将func2做为参数传递给func1,此时func=func1
#相当于func1返回了inner_func给a
#时刻牢记func2 与 func2()的区别
a()
#这里调用了inner_func
#输出:
#开始执行前
#我执行了func2
#执行结束

应该就能明白装饰器是什么了.所谓装饰器就是在闭包的基础上传递了一个函数,然后覆盖原来函数的执行入口,以后调用这个函数的时候,就可以额外实现一些功能了.装饰器的存在主要是为了不修改原函数的代码,也不修改其他调用这个函数的代码,就能实现功能的拓展.

当然装饰器还有更简介的写法,使用@语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def func1(func):
def inner_func():
print("开始执行前")
func()
print("执行结束")
return inner_func

@func1
def func2():
print("我执行了func2")

func2
#输出 <function func1.<locals>.inner_func at 0x00000000038DC268>
func2()
#这里相当于func2 = func1(func2)
#输出:
#开始执行前
#我执行了func2
#执行结束

装饰器还有很多其它特性,下次再另起一篇,这里算是开个头,困了,就到这里吧.

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)


 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?