赞
踩
如有转载请声明转载地址
如有侵权请联系本人
先检验一下自己的作用域基础:LEGB
def outer():
a = 10
inner()
def inner():
print(a)
a = 20
outer()
>>>
20
现在我们使用闭包来看一下
def outer():
a = 10
def inner():
print(a)
return inner
a = 20
outer()()
>>>
10
def outer(args):
a = 10
b = 15
c = 25
def inner():
return a + b + args
return inner
outer(5)()
上面的例子就是一个闭包,那么形成闭包的条件有两点:
闭包是一种延伸了作用域的函数
1.2 自由变量(free variable)
知道了闭包之后还要知道一个名词:自由变量free variable
的定义:
If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.
翻译一些就是如果一个变量出现在一个代码块中,那么它就是这个代码块的局部变量;如果一个变量出现在一个模块层级的作用域(py文件的最外层)中,那么它就是全局变量(模块的变量分为全局变量和局部变量)。如果一个变量被用在一个代码块中并且没有在这个代码块定义这个变量,那么它就是一个自由变量。
综上自由变量:没有在某代码块中定义,但却在该代码块中使用,也就是引用的外部的变量。
def f1():
a = 10
def f2():
print(a)
这就是一种自由变量的例子
通过__closure__
属性来判断
def outer(args):
a = 10
b = 15
c = 25
def inner():
return a + b + args
return inner
print(outer(5).__closure__)
>>>
(<cell at 0x00000276B79B01F8: int object at 0x00007FF8DE8EA2B0>, <cell at 0x00000276B97038E8: int object at 0x00007FF8DE8EA210>, <cell at 0x00000276B9833438: int object at 0x00007FF8DE8EA350>)
闭包函数和嵌套函数的区别在于闭包函数有一个 __closure__
属性,返回的是一个元组,每一项都是闭包函数引用的外部变量。可以通过cell_contents
将被引用的变量打印出来。
for line in outer(5).__closure__:
print(line.cell_contents)
>>
10 #a
5 #args
15 # b
看一下下面不满足闭包的情况以加深印象
#没有返回闭包函数名
def outer(args):
a = 10
def inner():
print(a + args)
inner()
print(outer(12).__closure__)
>>>
22
AttributeError: 'NoneType' object has no attribute '__closure__'
#没有引用外部函数作用域的变量或者形参
def outer(args):
a = 10
def inner():
print(10)
return inner
print(outer(12).__closure__)
>>>
None
懂得思考python解释器运行机制的小伙伴一定会产生这样的疑惑:
当outer(5)
结束之后返回了inner
,return
应该是把outer
函数给关闭了,它的本地作用域也随之消失,为什么
inner(3)
还能再次进入outer
?outer
的本地作用域调用a + b + args
我们知道当python程序运行时,编译的结果是保存在位于内存中的PyCodeObject
里,当python运行结束时,Python解释器则将PycodeObject
写回到pyc
文件中。pyc
文件是PyCodeObject
的一种持久化方式。
函数.__code__
属性可以访问PyCodeObject
,具体信息看下面的博客。
Python 中的代码对象 code object 与 code 属性_团子大圆帅的博客-CSDN博客_python code
我们要用的关键属性就是
def outer(args): a = 10 b = 15 c = 25 def inner(): name = '闭包' return a + b + args return inner #查看outer的代码对象 print(outer.__code__.co_varnames) print(outer.__code__.co_consts) >>> ('args', 'c', 'inner')#因为a,b被当成了自由变量 (None, 10, 15, 25, <code object inner at 0x000001FBC73B46F0, file "F:/Code/GIThub_100/Test6.py", line 48>, 'outer.<locals>.inner') #查看inner的局部变量 print(inner.__code__.co_varnames) >>>#只有一个 ('name',)
#查看inner的代码对象
inner = outer(5)
code_obj = inner.__code__
print(outer.__code__.co_cellvars)#outer被内层函数引用 变量
print(code_obj.co_freevars)#内层inner引用了外层的哪些变量--自由变量
>>>#结果一样的
('a', 'args', 'b')
('a', 'args', 'b')
至此不知道小伙伴们能否反映过来,这里已经解决了上面的两个疑惑。先将疑惑二,因为疑惑一不是闭包的特性而是嵌套函数的特性。
疑惑二:并且还能再次从outer
的本地作用域调用a + b + args
有没有发现在上面访问代码对象的时候我仅仅用了inner = outer(5)
+inner.__code__
,我根本没有再次进入outer
的内部,仍然可以通过code_obj.co_freevars
查看到inner引用的变量是啥?并且还能通过__closure__
查看自已引用的外部变量是哪些值。
print(inner.__closure__)
print(inner.__closure__[0].cell_contents)
print(inner.__closure__[1].cell_contents)
print(inner.__closure__[2].cell_contents)
>>>
10
5
15
也就是说在返回inner
之后并且再次进入outer
之前,这些被引用的自由变量(outer
的变量)已经归inner
所有了,官方一点就是闭包函数inner
引用 的自由变量在inner
被定义的时候就别存到了一个叫Cell
的对象中,如果后续闭包函数引用这些自由变量,就直接从Cell
中取。
inner(3)
还能再次进入outer
?#一步走看不出来outer的return已经执行完毕。
outer(5)()
#分步来走,说明确实是outer的return语句执行完毕后inner又进入的
inner = outer(5)
print(inner())
答案:这种特性不是闭包函数特有的而是所有嵌套函数在被外层函数返回函数对象后都有的特性。
#inner没有引用外部参数,这不是闭包
def outer():
a = 10
b = 11
def inner():
print("nishi ")
return inner
outer()()
>>>
nishi#一样运行成功
由闭包作用域我们再进一步看一个例子,这个例子是闭包典型的陷阱。
def outer():
f_list = []
for i in range(1,4):
def inner():
return i * i
f_list.append(inner)
return f_list
for fun in outer():
print(fun())
>>>
9
9
9
for
语句不存在局部作用域的说法,for循环中出现的变量包括循环变量都是和for
循环在同一个作用域下的即i
是outer
的局部变量。因此这里inner
引用了i
所以满足第二个条件。1,4,9
也就是说虽然引用了外部变量i
但是却没有把每一次的值都保存下来。原因是什么呢?
我们可以看出来所有返回的闭包函数使用的都是最后一次i
的值,这在for
循环中这种现象很常见很正规。我们来屡一下过程,每次for循环,都会执行def inner():
这一句话,执行完这一句话也就是简单的创建了一个inner
的函数对象,一共创建三个inner
。只不过每一次的循环变量i
都没有被保存下来,所以到最后outer的局部变量i
(当然也可以称为自由变量,这里大家知道就行)的值为3,也就是保存在每个inner的Cell中的i都是3(从这句话可以体会出,每次保存到Cell中的自由变量是return之后的值也就是inner被创建时的自由变量的值,只要没有return,闭包函数就没有被创建,在这之前自由变量可以被改变)
总结一下
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。