闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

上面这段话引自百度百科,涛涛觉得对于闭包的解释通俗易懂,言简意赅。

对于 Python ,涛涛目前研究不是很深,尚在学习当中,所以以下对 Python 闭包的解释案例多引自其他大神,自己也就搬搬砖,特此写下这篇,巩固一下自己的知识储备。

首先列出闭包的必要条件:

1、闭包函数必须返回一个函数对象

2、闭包函数返回的那个函数必须引用外部变量(一般不能是全局变量),而返回的那个函数内部不一定要return

以下是几个经典的闭包案例:

 # ENV>>> Python 3.6
# NO.1
def line_conf(a, b):
def line(x):
return a * x + b
return line # NO.2
def line_conf():
a = 1
b = 2 def line(x):
print(a * x + b)
return line # NO.3
def _line_(a,b):
def line_c(c):
def line(x):
return a*(x**2)+b*x+c
return line
return line_c

一、函数中的作用域

Python中函数的作用域由def关键字界定,函数内的代码访问变量的方式是从其所在层级由内向外的,如“热身”中的第一段代码:

   def line_conf(a, b):
def line(x):
return a * x + b
return line

嵌套函数line中的代码访问了a和b变量,line本身函数体内并不存在这两个变量,所以会逐级向外查找,往上走一层就找到了来自主函数line_conf传递的a, b。若往外直至全局作用域都查找不到的话代码会抛异常。

注意:不管主函数line_conf下面嵌套了多少个函数,这些函数都在其作用域内,都可以在line_conf作用域内被调用。

思考上面这段代码实现了什么功能?

     #定义两条直线
line_A = line_conf(2, 1) #y=2x+b
line_B = line_conf(3, 2) #y=3x+2 #打印x对应y的值
print(line_A(1)) #
print(line_B(1)) #

是否感觉“哎哟,有点意思~”,更有意思的在后面呢。

现在不使用闭包,看看需要多少行代码实现这个功能:

     def line_A(x):
return 2*x+1
def line_B(x):
return 3*x+2 print(line_A(1)) #
print(line_B(1)) #

不包括print语句的代码是4行,闭包写法是6行,看起来有点不对劲啊?怎么闭包实现需要的代码量还多呢?别急,我现在有个需求:

再定义100条直线!

那么现在谁的代码量更少呢?很明显这个是可以简单计算出来的,采用闭包的方式添加一条直线只需要加一行代码,而普通做法需要添两行代码,定义100条直线两种做法的代码量差为:100+6 -(100*2+4) = -98。需要注意的是,实际环境中定义的单个函数的代码量多达几十上百行,这时候闭包的作用就显现出来了,没错,大大提高了代码的可复用性!

注意:闭包函数引用的外部变量不一定就是其父函数的参数,也可以是父函数作用域内的任意变量,如“热身”中的第二段代码:

 def line_conf():
a = 1
b = 2 def line(x):
print(a * x + b)
return line

二、如何显式地查看“闭包”

接上面的代码块:

     L = line_conf()
print(line_conf().__closure__) #(<cell at 0x05BE3530: int object at 0x1DA2D1D0>,
# <cell at 0x05C4DDD0: int object at 0x1DA2D1E0>)
for i in line_conf().__closure__: #打印引用的外部变量值
print(i.cell_contents) #1 ; #2

__closure__属性返回的是一个元组对象,包含了闭包引用的外部变量。

若主函数内的闭包不引用外部变量,就不存在闭包,主函数的_closure__属性永远为None:

     def line_conf():
a = 1
b = 2
def line(x):
print(x+1) #<<<------
return line
L = line_conf()
print(line_conf().__closure__) # None
for i in line_conf().__closure__: #抛出异常
print(i.cell_contents)

若主函数没有return子函数,就不存在闭包,主函数不存在_closure__属性:

 def line_conf():
a = 1
b = 2
def line(x):
print(a*x+b)
return a+b #<<<------
L = line_conf()
print(line_conf().__closure__) # 抛出异常

三、为何叫闭包?

 

先看代码:

     def line_conf(a):
b=1
def line(x):
return a * x + b
return line line_A = line_conf(2)
b=20
print(line_A(1)) #

如你所见,line_A对象作为line_conf返回的闭包对象,它引用了line_conf下的变量b=1,在print时,全局作用域下定义了新的b变量指向20,最终结果仍然引用的line_conf内的b。这是因为,闭包作为对象被返回时,它的引用变量就已经确定(已经保存在它的__closure__属性中),不会再被修改。

是的,闭包在被返回时,它的所有变量就已经固定,形成了一个封闭的对象,这个对象包含了其引用的所有外部、内部变量和表达式。当然,闭包的参数例外。

四、闭包可以保存运行环境

思考下面的代码会输出什么?

     _list = []
for i in range(3):
def func(a):
return i+a
_list.append(func)
for f in _list:
print(f(1))

1 , 2,  3吗?如果不是又该是什么呢?    结果是3, 3, 3 。

因为,在Python中,循环体内定义的函数是无法保存循环执行过程中的不停变化的外部变量的,即普通函数无法保存运行环境!想要让上面的代码输出1, 2, 3并不难,“术业有专攻”,这种事情该让闭包来:

     _list = []
for i in range(3):
def func(i):
def f_closure(a): # <<<---
return i + a
return f_closure
_list.append(func(i)) # <<<--- for f in _list:
print(f(1))


五、闭包的实际应用

现在你已经逐渐领悟“闭包”了,趁热打铁,再来一个小例子:

     def who(name):
def do(what):
print(name, 'say:', what) return do lucy = who('lucy')
john = who('john') lucy('i want drink!')
lucy('i want eat !')
lucy('i want play !') john('i want play basketball')
john('i want to sleep with U,do U?') lucy("you just like a fool, but i got you!")

看到这里,你也可以试着自己写出一个简单的闭包函数。

OK,现在来看一个真正在实际环境中会用到的案例:

1、【闭包实现快速给不同项目记录日志】

     import logging
def log_header(logger_name):
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(name)s] %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
logger = logging.getLogger(logger_name) def _logging(something,level):
if level == 'debug':
logger.debug(something)
elif level == 'warning':
logger.warning(something)
elif level == 'error':
logger.error(something)
else:
raise Exception("I dont know what you want to do?" )
return _logging project_1_logging = log_header('project_1') project_2_logging = log_header('project_2') def project_1(): #do something
project_1_logging('this is a debug info','debug')
#do something
project_1_logging('this is a warning info','warning')
# do something
project_1_logging('this is a error info','error') def project_2(): # do something
project_2_logging('this is a debug info','debug')
# do something
project_2_logging('this is a warning info','warning')
# do something
project_2_logging('this is a critical info','error') project_1()
project_2()
 #输出
2018-05-26 22:56:23 [project_1] DEBUG this is a debug info
2018-05-26 22:56:23 [project_1] WARNING this is a warning info
2018-05-26 22:56:23 [project_1] ERROR this is a error info
2018-05-26 22:56:23 [project_2] DEBUG this is a debug info
2018-05-26 22:56:23 [project_2] WARNING this is a warning info
2018-05-26 22:56:23 [project_2] ERROR this is a critical info

这段代码实现了给不同项目logging的功能,只需在你想要logging的位置添加一行代码即可。

扩展: python中的装饰器特性就是利用闭包实现的,只不过用了@作为语法糖,使写法更简洁。如果掌握了闭包,接下来就去看一下装饰器,也会很快掌握的。

python闭包到底有什么作用

 

1、global关键字的作用

如果在函数中需要修改全局变量,则需要使用该关键字,具体参见下面例子。

 variable=100
def function():
print(variable) #在函数内不对全局变量修改,直接访问是没问题的,不会报错
function() #输出100
 variable=100
def function():
result = variable + 111
print(result) #在函数内不对全局变量修改,直接访问是没问题的,不会报错
function() #输出100
 variable = 100
def function():
variable += 111
print(variable) # 显示local variable 'variable' referenced before assignment。
# 即在函数局部作用域中直接改变全局变量的值会报错
function()
 variable = 100
def function():
variable = 1000 # 此时修改variable变量的值不会报错,因为已经在函数局部作用域内重新定义variable,会覆盖全局variable。
variable += 111
print(variable)
function() # 输出1111
print(variable) # 输出100,虽然函数内部重新覆盖了variable,但是全局variable并未变,依然还是100

那如果不再函数内部重新为全局变量赋值,又想改变全局变量的值,应该怎么做呢?这就要使用global关键字了,如下。

 variable = 100
def function():
global variable # 使用global关键字,表明variable是全局的,此时就可以了直接在函数局部作用域内改变variable的值了
variable += 111
print(variable) # 输出211
function()
print(variable) # 输出211,这和上面的不一样了,发现全局变量variable本身也改变了

总结:global的作用就是在“函数局部作用域”内声明表示一个全局变量,从而可以在函数内部修改全局变量的值(否则只能访问不能修改),而且函数内部改变的全局变量的值也会改变。

2、函数局部作用域

函数的局部作用域是不能够保存信息的,即在函数内部声明变量在函数调用结束之后函数里面保存的信息就被销毁了,包括函数的参数,如下。

 def fun(step):
num = 1
num += step
print(num)
i = 1
while (i < 5):
fun(3) # 连续调用函数4次,每次输出的值都是4,即3+1,这说明每次调用fun函数之后,函数内部定义局部变量num就被销毁了,
# 没有保存下来,说明函数的局部作用域被销毁了。那如果要保存函数的局部变量,怎么办呢?引入“闭包”。
i += 1

3、闭包——装饰器的本质也是闭包

 

“闭包”的本质就是函数的嵌套定义,即在函数内部再定义函数,如下所示。

“闭包”有两种不同的方式,第一种是在函数内部就“直接调用了”;第二种是“返回一个函数名称”。

(1)第一种形式——直接调用

 def Maker(name):
num=100
def func1(weight,height,age):
weight+=1
height+=1
age+=1
print(name,weight,height,age)
func1(100,200,300) #在内部就直接调用“内部函数”
Maker('feifei') #调用外部函数,输出 feifei 101 201 301

(2)第二种形式——返回函数名称

 def Maker(name):
num=100
def func1(weight,height,age):
weight+=1
height+=1
age+=1
print(name,weight,height,age)
return func1 #此处不直接调用,而是返回函数名称(Python中一切皆对象)
maker=Maker('feifei') #调用包装器
maker(100,200,300) #调用内部函数

(3)“闭包”的作用——保存函数的状态信息,使函数的局部变量信息依然可以保存下来,如下。

 def Maker(step): #包装器
num=1
def fun1(): #内部函数
nonlocal num #nonlocal关键字的作用和前面的local是一样的,如果不使用该关键字,则不能再内部函数改变“外部变量”的值
num=num+step #改变外部变量的值(如果只是访问外部变量,则不需要适用nonlocal)
print(num)
return fun1
#=====================================#
j=1
func2=Maker(3) #调用外部包装器
while(j<5):
func2() #调用内部函数4次 输出的结果是 4、7、10、13
j+=1

从上面的例子可以看出,外部装饰器函数的局部变量num=1、以及调用装饰器Maker(3)时候传入的参数step=3都被记忆了下来,所以才有1+3=4、4+3=7、7+3=10、10+3=13.

从这里可以看出,Maker函数虽然调用了,但是它的局部变量信息却被保存了下来,这就是“闭包”的最大的作用——保存局部信息不被销毁。

4、nonlocal关键字的作用

该关键字的作用和local的作用类似,就是让“内部函数”可以修改“外部函数(装饰器)”的局部变量值。详细信息这里不做讨论。

以上就是 Python 闭包学习记录的内容,在此特别感谢 CSDN 的 chaseSpace-L 和 LoveMIss-Y 两位大神的文章和解释!

Python 闭包小记的更多相关文章

  1. JavaScript 闭包小记

    最近朋友面试被问到了 JS 闭包的问题,本人一时语塞,想起了袁华的一句话:“这道题太难了,我不会做,不会做啊!”. JS 闭包属于面向对象的一个重要知识点,特此本人又开始了一段说走就走的旅程. 闭包就 ...

  2. Python 闭包

    什么是闭包? 闭包(closure)是词法闭包(lexical closure)的简称.闭包不是新奇的概念,而是早在高级程序语言开始发展的年代就已产生. 对闭包的理解大致分为两类,将闭包视为函数或者是 ...

  3. Python闭包与函数对象

    1. Python闭包是什么 在python中有函数闭包的概念,这个概念是什么意思呢,查看Wikipedia的说明如下: “ In programming languages, closures (a ...

  4. Python闭包及装饰器

    Python闭包 先看一个例子: def outer(x): def inner(y): return x+y return innder add = outer(8) print add(6) 我们 ...

  5. Python闭包详解

    Python闭包详解 1 快速预览 以下是一段简单的闭包代码示例: def foo(): m=3 n=5 def bar(): a=4 return m+n+a return bar >> ...

  6. Python闭包及其作用域

    Python闭包及其作用域 关于Python作用域的知识在python作用域有相应的笔记,这个笔记是关于Python闭包及其作用域的详细的笔记 如果在一个内部函数里,对一个外部作用域(但不是全局作用域 ...

  7. 理解Python闭包概念

    闭包并不只是一个python中的概念,在函数式编程语言中应用较为广泛.理解python中的闭包一方面是能够正确的使用闭包,另一方面可以好好体会和思考闭包的设计思想. 1.概念介绍 首先看一下维基上对闭 ...

  8. Python闭包举例

    Python闭包的条件: 1.函数嵌套.在外部函数内,定义内部函数. 2.参数传递.外部函数的局部变量,作为内部函数参数. 3.返回函数.外部函数的返回值,为内部函数. 举例如下: def line_ ...

  9. python 闭包和装饰器

    python 闭包和装饰器 一.闭包闭包:外部函数FunOut()里面包含一个内部函数FunIn(),并且外部函数返回内部函数的对象FunIn,内部函数存在对外部函数的变量的引用.那么这个内部函数Fu ...

随机推荐

  1. java编程思想-第六章-某些练习题

    参考https://blog.csdn.net/caroline_wendy/article/details/47271037 3 package debug; import java.util.Ar ...

  2. 根据http协议下载文件保存到相应的文件下

    本实例通过提供的http网址来下载文件,并保存到本地指定的文件下. 本例提供的网址为:http://112.53.80.131:8888/database/11.mdb,下载的文件名为:11.mdb ...

  3. loj548 「LibreOJ β Round #7」某少女附中的体育课

    这道题好神啊!!! 发现这题就是定义了一种新的卷积,然后做k+1次卷积. 这里我们就考虑构造一个变换T,使得$T(a) \cdot T(b) =T(a∘b)$,这里是让向量右乘这个转移矩阵. 于是我们 ...

  4. ISCC 2018(数字密文)

    做过iscc 2018之后有了很多的感触,也有更多的了解自己的不足之处,整理了一下web的wp, 为了保证各位小伙伴的阅读质量,我将会把wp以每一道题一个博文的形式写出来,希望能够帮助到你们 其中的步 ...

  5. 回归树(Regression Tree)

    目录 回归树 理论解释 算法流程 ID3 和 C4.5 能不能用来回归? 回归树示例 References 说到决策树(Decision tree),我们很自然会想到用其做分类,每个叶子代表有限类别中 ...

  6. Supervisor守护DotNet Core控制台程序

    Supervisor 相信对Linux系统很熟的都知道这个软件,基于Python写的一个守护进程软件.具体的介绍和使用我就不再赘述了. 使用asp.net core 部署在Linux常用的方法 我们可 ...

  7. 全面理解 javascript 的 argements caller callee call apply 之caller

    /** * 演示arguments的用法,如何获取实参数和形数数 */ function argTest(a,b,c,d){ var numargs = arguments.length; // 获取 ...

  8. 事务是什么?事务的4个特点(ACID),事务的开启与结束

    事务是指作为单个逻辑工作单元执行的一组相关操作.这些操作要求全部完成 或者全部不完成.使用事务是为了保证数据的安全有效. 事务有一下四个特点:(ACID) 1.原子性(Atomic):事务中所有数据的 ...

  9. Nginx 配置 Https 免费证书访问

    配置HTTPS 现在做博客或者做网站没有 https 已经不行了,就记录一下我在腾讯云配置 https 的过程吧,非常简单,1个小时就可以了. 还涉及到 http 访问自动转发到 https 访问路径 ...

  10. 浅谈unity中gamma空间和线性空间

    转载请标明出处:http://www.cnblogs.com/zblade/ 一.概述 很久没有写文章了,今天写一篇对gamma空间和线性空间的个人理解总结,在查阅和学习了各个资料后,算是一个个人笔记 ...