最近在学习 Python 的过程中遇到了闭包这一概念,现总结如下:
首先什么是闭包呢
咱们看看下面一段代码:
def greetingConfig(prefix):
def greeting(postfix):
print(prefix, postfix)
return greeting
m = greetingConfig('Good morning!')
m('July')
m('Mike')
这段代码实现了一个打招呼的函数 greetingConfig(),实现该程序的结果为:
Good morning! July
Good morning! Mike
在以上程序中,greetingConfig() 函数嵌套 greeting() 函数,且 greeting() 函数的引用作为返回值。greeting() 函数访问上一级函数的变量 prefix。此时,greetingConfig() 函数就是闭包。
闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用自由变量的函数。这个被引用的自由变量将与这个函数一同存在,即使已经离开创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。
闭包是函数中提出的一个概念,要形成一个完整严格的闭包,需要满足以下条件(三个条件,缺一不可):
- 必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套
- 外部函数有返回值,且返回的值是:内部函数的引用(即内部函数的地址)
- 内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量(满足这个条件才能算是严格的闭包)
格式
def 外部函数():
...
def 内部函数():
...
return 内部函数引用
作用
- 闭包主要用在函数式编程上
- 可以根据外部作用域的局部变量来得到不同的结果
比如:求一个点到其他点之间的距离(不使用闭包,使用闭包分别验证)
import math
# 不使用闭包需要4个参数
def dis_bt_point(x1, y1, x2, y2):
return math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2))
# 不使用闭包的调用求距离函数
print('点(10,10)到坐标(0,0)的距离:{:.2f}'.format(dis_bt_point(0, 0, 10, 10)))
print('点(20,10)到坐标(0,0)的距离:{:.2f}'.format(dis_bt_point(0, 0, 20, 10)))
# 闭包
def disOut(x1, y1):
def disIn(x2, y2):
return math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2))
return disIn
distance = disOut(0, 0)
# 获取点(10,10)到坐标(0,0)的距离
print('点(10,10)到坐标(0,0)的距离:{:.2f}'.format(distance(10, 10)))
# 获取点(20,10)到坐标(0,0)的距离
print('点(20,10)到坐标(0,0)的距离:{:.2f}'.format(distance(20, 10)))
运行结果:
点(10,10)到坐标(0,0)的距离:14.14
点(20,10)到坐标(0,0)的距离:22.36
点(10,10)到坐标(0,0)的距离:14.14
点(20,10)到坐标(0,0)的距离:22.36
- 在不修改源代码的情况下增加函数功能
比如:为调用的函数增加日志功能,记录访问的函数名以及访问函数的时间
import time
def write_log(func):
try:
with open('log.txt', 'a', encoding='utf-8') as f:
# 写入相关数据信息(访问的函数名,访问的时间)
f.write(func.__name__)
f.write('\t')
# 写入访问时间
f.write(time.asctime())
f.write('\n')
except Exception as e:
print(e)
# 闭包
def funcOut(func):
def wrapper():
write_log(func)
func()
return wrapper
def func1():
print('功能1')
def func2():
print('功能2')
if __name__ == '__main__':
func1()
func2()
运行结果:
- 控制台:
功能1
功能2
- log.txt:
func1 Mon Dec 7 21:39:27 2020
func2 Mon Dec 7 21:39:27 2020
闭包中的关键部分—内部函数
特点
- 可以访问外部函数的变量
- 内部函数可以修改外部函数的可变变量比如: list
- 内部函数修改全局的不可变变量时,需要在内部函数声明:global 变量名
- 内部函数修改外部函数的不可变变量时,需要在内部函数中声明:nonlocal 变量名
- locals() 查看本地变量有哪些,以字典的形式输出
- globals() 查看全局变量有哪些,以字典的形式输出(注意里面会有一些系统的键值对)
示例代码
a = 100
def func():
b = 99
def inner_func():
global a
nonlocal b
c = 88
a += 10
b += 1
c += 12
print(a, b, c)
inner_func()
print(locals())
print(globals())
if __name__ == '__main__':
func()
# 运行结果:
110 100 100
{'inner_func': <function func.<locals>.inner_func at 0x0000020B1CF02378>, 'b': 100}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002517AA6B4A8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/PythonProject/test.py', '__cached__': None, 'math': <module 'math' (built-in)>, 'random': <module 'random' from 'E:\\python\\lib\\random.py'>, 'quadratic': <function quadratic at 0x000002517A961E18>, 'a': 110, 'func': <function func at 0x000002517AD222F0>}
示例代码(简单应用:计数器)
def counter():
count = [0]
def add_one():
count[0] += 1
print('当前是第{}次访问'.format(count[0]))
return add_one
if __name__ == '__main__':
count = counter()
count()
count()
count()
# 运行结果:
当前是第1次访问
当前是第2次访问
当前是第3次访问
闭包中内部函数访问外部函数内的变量时,内部函数在调用完后并不会立即将该内存空间回收,而是会一直保持调用时的状态。用不同的参数值进行调用时,会另外开辟一块内存空间进行存储,而不会影响之前的引用内容。
总结
- 由于闭包引用了外部函数的局部变量,使外部函数的局部变量没有及时释放,消耗内存
- 作用域没有那么直观
- 闭包的好处,使代码变得简洁,便于阅读代码。
- 闭包是理解装饰器的基础