闭包就是能够读取其他函数内部变量的函数。例如在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. rabbit入门教程

    简介 rabbitmq是一个消息代理系统,为应用提供一个通用得消息发布,接受平台,为应用提供非阻塞的消息系统,方便进行异步处理. 优点 消息的可靠性.持久化消息,消息接受确认,消息重传等可靠机制. 灵 ...

  2. procotol.go 源码阅读

    )         return     }     bufferOver = buffer[i:]     return } //整形转换成字节 // func IntToBytes(n int) ...

  3. election.go

    package)) ].GetResponseRange().Kvs[] ) ].Value),]         }         wch := client.Watch(cctx, string ...

  4. HEOI2018——welcome to NOI2018

    我不得不和烈士和小丑走在同一道路上,  万人都要将火熄灭,  我一人独将此火高高举起,  我借此火得度一生的茫茫黑夜. ——海子 弹指一瞬间,翘首以盼的HEOI2018就来了. 我,一个滑稽的小丑,带 ...

  5. 【bzoj 2916】[Poi1997]Monochromatic Triangles

    题目描述        空间中有n个点,任意3个点不共线.每两个点用红线或者蓝线连接,如果一个三角形的三边颜色相同,那么称为同色三角形.给你一组数据,计算同色三角形的总数.          输入 第 ...

  6. 【Unity游戏开发】AssetBundle杂记--AssetBundle的二三事

    一.简介 马三在公司大部分时间做的都是游戏业务逻辑和编辑器工具等相关工作,因此对Unity AssetBundle这块的知识点并不是很熟悉,自己也是有打算想了解并熟悉一下AssetBundle,掌握一 ...

  7. java代码之美(11)---java代码的优化

    java代码的优化 随着自己做开发时间的增长,越来越理解雷布斯说的: 敲代码要像写诗一样美.也能理解有一次面试官问我你对代码有洁癖吗? 一段好的代码会让人看就像诗一样,也像一个干净房间会让人看去很舒服 ...

  8. NIO(生活篇)

    今晚是个下雨天,写完今天最后一行代码,小鲁班起身合上电脑,用滚烫的开水为自己泡制了一桶老坛酸菜牛肉面.这大概是苦逼程序猿给接下来继续奋战的自己最好的馈赠.年轻的程序猿更偏爱坐在窗前,在夜晚中静静的享受 ...

  9. Android Studio Run项目出现Failure [INSTALL_FAILED_TEST_ONLY]

    同名掘金博文:https://juejin.im/post/5c2e0c496fb9a049a711f09a 运行环境: AS 版 本:Android Studio 3.2.1 手机型号:vivo Y ...

  10. C#开发APP,ToolBar控件在Smobiler中的使用方式【附案例源码】——Smobiler移动开发平台

    控件说明 底部工具栏控件. 效果演示 其他效果 该界面为仿淘宝UI制作的一个简单的UI模板,源码获取方式请拉至文章末尾. 特色属性 属性 属性说明 Direction(相对布局) 容器主轴方向. Fl ...