闭包与装饰器
首先闭包并不仅是一个Python中的概念,在函数式编程语言中应用较为广泛。理解Python中的闭包一方面是能够正确的使用闭包,另一方面可以好好体会和思考闭包的设计思想。
概念介绍
首先看一下维基上对闭包的解释:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包。实际上闭包可以看做一种更加广义的函数概念。因为其已经不再是传统意义上定义的函数。
根据我们对编程语言中函数的理解,大概印象中的函数是这样的:
程序被加载到内存执行时,函数定义的代码被存放在代码段中。函数被调用时,会在栈上创建其执行环境,也就是初始化其中定义的变量和外部传入的形参以便函数进行下一步的执行操作。当函数执行完成并返回函数结果后,函数栈帧便会被销毁掉。函数中的临时变量以及存储的中间计算结果都不会保留。下次调用时唯一发生变化的就是函数传入的形参可能会不一样。函数栈帧会重新初始化函数的执行环境。
初探闭包
def outer(outNum): def inner(innerNum): print(f"inner nums:{innerNum}") return innerNum + outNum return inner print(outer(1)(2))# inner nums:2# 3
虽然Python中的闭包并没有使用到匿名函数,但其本质却是一样的。为什么呢?
嵌套函数,均是函数中返回函数
个人认为理解闭包难点有二:
- 嵌套函数,造成一时难以理解。
“一切皆对象”
- 作用域,就近原则。
先代码块、后局部、最后全局
概念的性的东西,可能需要自己加把劲儿~。需要自己理解
闭包作用域
def outer_func(): loc_list = [] def inner_func(name): loc_list.append(len(loc_list) + 1) print(f'{name}-loc_list: {loc_list}') return inner_func clo_func_0 = outer_func() clo_func_0('clo_func_0') clo_func_0('clo_func_0') clo_func_0('clo_func_0') clo_func_1 = outer_func() clo_func_1('clo_func_1') clo_func_0('clo_func_0') clo_func_1('clo_func_1')
clo_func_0-loc_list: [1]
clo_func_0-loc_list: [1, 2]
clo_func_0-loc_list: [1, 2, 3]
clo_func_1-loc_list: [1]
clo_func_0-loc_list: [1, 2, 3, 4]
clo_func_1-loc_list: [1, 2]
在python中我们称上面的这个loc_list为闭包函数inner_func的一个自由变量(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.
- 闭包中的引用的自由变量只和具体的闭包有关联,闭包的每个实例引用的自由变量互不干扰。
- 一个闭包实例对其自由变量的修改会被传递到下一次该闭包实例的调用。
闭包陷阱
def outer_func(*args): fs = [] for i in range(3): def inner_func(): return i * i fs.append(inner_func) return fs fs1, fs2, fs3 = outer_func() print(fs1()) print(fs2()) print(fs3())
全部输出4
上面这段代码可谓是典型的错误使用闭包的例子。程序的结果并不是我们想象的结果0,1,4。实际结果全部是4。
这个例子中,outer_func返回的并不是一个简单的闭包函数,而是一个包含三个闭包函数的一个list。这个例子中比较特殊的地方就是返回的所有闭包函数均引用父函数中定义的同一个自由变量。
但这里的问题是为什么for循环中的变量变化会影响到所有的闭包函数?尤其是我们上面刚刚介绍的例子中明明说明了同一闭包的不同实例中引用的自由变量互相没有影响的。而且这个观点也绝对的正确。
那么问题到底出在哪里?应该怎样正确的分析这个错误的根源。
其实问题的关键就在于在返回闭包列表fs之前for循环的变量的值已经发生改变了,而且这个改变会影响到所有引用它的内部定义的函数。因为在函数outer_func返回前其内部定义的函数并不是闭包函数,只是一个内部定义的函数。
重要的是理解的基础上灵活的应用解决实际的问题并避免陷阱
自定义闭包
其实自定义闭包很简单,就是嵌套函数+ 作用域。
外面的函数return内部的对象,执行内部的函数。
自己手写一个比闭包函数
def outer_func(): def inner_func(): # 代码逻辑 return return inner_func
- 定义外部函数(outer_func)
- 书写
此函数全局“环境”
retrun
inner_func
- 书写inner_func,函数逻辑
- return 最终结果
装饰器
装饰器是程序开发中经常用到的一个功能。用好装饰器,开发,测试,异常效率大大的增加。尤其是Python中的语法糖更是让装饰器炫酷的不行
装饰器主要的功能:
- 函数执行效率测试
- 权限检测、缓存、日志
- 执行函数钱预处处理、执行函数后清理
- 等等
装饰器的与钩子(Hook)
的原理基本一致。即不改变函数内部的代码,实现改写,新增等效果
说了这么多让我们,来实现一下吧
使用装饰器
使用语法糖@
:如下(建议)
# 无参数@装饰器函数名def f(): pass# 执行被装饰过的函数 f()# 有参数@装饰器函数名(参数)def f(): pass# 执行被装饰过的函数 f()
不使用语法糖@
# 无参数装饰器函数名(被装饰函数名)# 有参数(装饰器函数名(参数))(被装饰函数名)
语法糖:在Python实际工作中,通常使用
@
符来调用装饰器
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
无参数装饰
开箱即用的运行时间检测,紧缺到小数点后后十位。
from time import time, sleepdef outer(func: object) -> None: """ :param func: object :return: None """ def inner(): start = time() print(f"{func.__name__} start running at:{start}") func() end = time() - start print("%s using time:%.10f" % (func.__name__, end)) return inner
调用
使用@符号语法糖
```p y
@outer
def test():
print(“HelloWorld”)
sleep(3)
不使用@符号语法糖 ```python def test(): print("HelloWorld") sleep(3) outer(test)()
有参数装饰
from time import time, sleepdef Decorator(func: object): """ :param func: object :return: """ def function(x, y): start = time() print(f"{func.__name__} start running at:{start}") print(x, y) func(x, y) end = time() - start print("%s using time:%.10f" % (func.__name__, end)) return function@Decoratordef func(x, y): print("i am func", x ** y) func(2, 3)
不定参数装饰
from time import time, sleep def Decorator(func: object): """ :param func: object :return: """ def function(*args, **kwargs): start = time() print(f"{func.__name__} start running at:{start}") func(*args, *kwargs) end = time() - start print("%s using time:%.10f" % (func.__name__, end)) return function @Decorator def func(x, y): print("i am func", x ** (x + y)) func(2, 3)
带参数的装饰器
有没有方法能让装饰器带其他参数呢?比如字符串参数等。答案是可以的,只需在最外层再封装一个函数即可。如下
from time import time, sleepdef argsDecorator(string): print(string) def Decorator(func: object): """ :param func: object :return: """ def function(*args, **kwargs): start = time() print(f"{func.__name__} start running at:{start}") func(*args, *kwargs) end = time() - start print("%s using time:%.10f" % (func.__name__, end)) return function return Decorator@argsDecorator("using here")def func(x, y): print(x ** y) func(2, 3)
多重装饰
from functools import wrapsdef decorator1(func): @wraps(func) def log(*args, **kwargs): # do some things here, for example, add some log print("function {} was called in decorator1".format(func.__name__)) return func(*args, **kwargs) return logdef decorator2(func): @wraps(func) def another_log(*args, **kwargs): # do some things here, for example, add some log print("function {} was called in decorator2".format(func.__name__)) return func(*args, **kwargs) return another_log@decorator1@decorator2def add(a, b): return a + b add(1, 2)@decorator2@decorator1def minus(a, b): return a - b minus(3, 4) print(minus(3, 4))
装饰器类
此来自于https://www.runoob.com/w3cnote/python-func-decorators.html
from functools import wrapsclass logit(object): def __init__(self, logfile='out.log'): self.logfile = logfile def __call__(self, func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " was called" print(log_string) # 打开logfile并写入 with open(self.logfile, 'a') as opened_file: # 现在将日志打到指定的文件 opened_file.write(log_string + '\n') # 现在,发送一个通知 self.notify() return func(*args, **kwargs) return wrapped_function def notify(self): passclass email_logit(logit): """ 一个logit的实现版本,可以在函数调用时发送email给管理员 """ def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_logit, self).__init__(*args, **kwargs) def notify(self): # 发送一封email到self.email pass