前言
闭包作为python高级特性中的一个,初学总觉其披着一层神秘的面纱,这里我们来一起揭开这层面纱吧。那什么是闭包呢?
闭包,又称闭包函数,和普通的嵌套函数类似,闭包中包含外部函数和内部函数,不同之处是闭包中外部函数返回的不是一个值,而是内部函数,即闭包对象;通常,该返回的函数会被赋值给一个变量,继而被调用执行。
为何要是用闭包
比如,已知三条直线上某一个点的横坐标x,求其纵坐标y。如果不使用闭包,实现如下:
def line1(x):
return 2 * x + 1
def line2(x):
return 3 * x + 2
def line3(x):
return 5 * x + 3
if __name__ == '__main__':
print(line1(2))
print(line2(2))
print(line3(2))
使用闭包,实现如下
# 闭包
def line_closure(a, b):
def line(x):
return a * x + b
return line
if __name__ == '__main__':
line4 = line_closure(2, 1) # 返回闭包对象,赋值给line4
print(line4(2)) # 此处可调用闭包里的内部函数
line5 = line_closure(3, 2)
print(line5(2))
line6 = line_closure(5, 3)
print(line6(2))
5 8 13 5 8 13
以上两种方法结果一样,在只有三条直线的情况下,代码量区别不大,而当有几百条直线呢,我们是不是还要定义几百个函数呢,此时用闭包就会使代码变得简单美观,提高了代码的复用率。
闭包应用举例
给不同项目记录日志
import logging
def log_header_closure(project_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(project_name)
def _logging(message, level):
if level == 'debug':
logger.debug(message)
elif level == 'warning':
logger.warning(message)
elif level == 'error':
logger.error(message)
return _logging
if __name__ == '__main__':
project_1_logger = log_header_closure('project_1')
project_1_logger('this is a warning info', 'warning')
project_1_logger('this is a error info', 'error')
project_2_logger = log_header_closure('project_2')
project_2_logger('this is a warning info', 'warning')
project_2_logger('this is a error info', 'error')
2021-10-27 14:21:32 [project_1] WARNING this is a warning info
2021-10-27 14:21:32 [project_1] ERROR this is a error info
2021-10-27 14:21:32 [project_2] WARNING this is a warning info
2021-10-27 14:21:32 [project_2] ERROR this is a error info
闭包和装饰器
其实,装饰器就是闭包,只不过装饰器的外部函数参数是一个函数,内部函数对该函数进行加工修饰最后返回。
def add(x, y):
return x + y
# 装饰器,实际上就是一个闭包
def wrap_outer(func):
def wrap_inner(x, y):
print(time.asctime())
print('run start')
result = func(x, y)
print('run finished')
print(time.asctime())
return result
return wrap_inner
if __name__ == '__main__':
wrapper = wrap_outer(add)
print(wrapper(2, 3))
Wed Oct 27 11:59:19 2021
run start
run finished
Wed Oct 27 11:59:19 2021
5
接着,我们用python装饰器的“语法糖”来重新实现一下,让代码看起来更优雅些:
from functools import wraps
def wrap_outer(func):
@wraps(func)
def wrap_inner(*args, **kwargs):
print(time.asctime())
print('run start')
result = func(*args, **kwargs)
print('run finished')
print(time.asctime())
return result
return wrap_inner
@wrap_outer
def add(x, y):
return x + y
if __name__ == '__main__':
print(add(2, 3))
Wed Oct 27 13:39:55 2021
run start
run finished
Wed Oct 27 13:39:55 2021
5
可以看出,执行结果跟上面闭包实现一模一样。
闭包和普通函数
闭包比普通函数多了一个'__closure__'属性,用来记录引用的外部函数的自由变量的地址。当闭包被调用时,会从该地址获取到该自由变量,并完成整体的函数调用。
如上面的日志例子:
project_1_logger = log_header_closure('project_1')
print('111', project_1_logger.__closure__)
(<cell at 0x03AF7370: Logger object at 0x0E56D1B0>,)