学习目标 :掌握装饰器相关知识点
今天来学函数的第二个高阶应用,装饰器 ,装饰器属于Python的核心特色,学了这两个高阶功能,我们就可以做更多事情了。
装饰器是Python中 用一个函数装饰另外一个函数或类并可以在不改变函数或方法的调用方式的情况下,为其提供额外功能的语法现象。 装饰器本身是一个函数,只不过该函数可以具有特殊的含义,它的参数是被装饰的函数或类,它的返回值是一个带有装饰功能的函数。装饰器用来 装饰函数或类, 使用装饰器可以在函数执行前和执行后添加相应操作**。换句话说,装饰器通过返回包装对象实现间接调用,以此在函数执行前或执行后插入额外逻辑。很显然,装饰器是一个高阶函数,它的参数和返回值都是函数。下面我们先通过一个简单的例子来说明装饰器的写法和作用。
一、案例引入 示例1 :我们假设你的程序实现了say_hello()
和say_goodbye()
两个函数。
1 2 3 4 5 6 7 8 9 def say_hello () : print("hello!" ) def say_goodbye () : print("hello!" ) if __name__ == '__main__' : say_hello() say_goodbye()
但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()
出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:
1 2 3 4 [DEBUG]: Enter say_hello() Hello! [DEBUG]: Enter say_goodbye() Goodbye!
好,小A是个毕业生,他是这样实现的。
1 2 3 4 5 6 7 8 9 10 11 def say_hello () : print("[DEBUG]: enter say_hello()" ) print("hello!" ) def say_goodbye () : print("[DEBUG]: enter say_goodbye()" ) print("hello!" ) if __name__ == '__main__' : say_hello() say_goodbye()
很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def debug () : import inspect caller_name = inspect.stack()[1 ][3 ] print("[DEBUG]: enter {}()" .format(caller_name)) def say_hello () : debug() print("hello!" ) def say_goodbye () : debug() print("goodbye!" ) if __name__ == '__main__' : say_hello() say_goodbye()
是不是好一点?那当然,但是每个业务函数里都要调用一下debug()
函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?
那么装饰器这时候应该登场了。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能 。
在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。
1 2 3 4 5 6 7 8 9 10 def debug (func) : def wrapper () : print("[DEBUG]: enter {}()" .format(func.__name__)) return func() return wrapper def say_hello () : print("hello!" ) say_hello = debug(say_hello)
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
1 2 3 4 5 6 7 8 9 def debug (func) : def wrapper () : print("[DEBUG]: enter {}()" .format(func.__name__)) return func() return wrapper @debug def say_hello () : print("hello!" )
这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper
接受和原函数一样的参数,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def debug (func) : def wrapper (*args, **kwargs) : print("[DEBUG]: enter {}()" .format(func.__name__)) print('Prepare and say...' ,) return func(*args, **kwargs) return wrapper @debug def say (something) : print("hello {}!" .format(something)) if __name__ == '__main__' : say('你好啊' )
这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args
和关键字参数**kwargs
,有了这两个参数,装饰器就可以用于任意目标函数了。
1 2 3 4 5 6 7 8 9 10 def debug (func) : def wrapper (*args, **kwargs) : print "[DEBUG]: enter {}()" .format(func.__name__) print 'Prepare and say...' , return func(*args, **kwargs) return wrapper @debug def say (something) : print "hello {}!" .format(something)
至此,你已完全掌握初级的装饰器写法。
示例2 :假设已经有名为downlaod
和upload
的两个函数,分别用于文件的上传和下载,下面的代码用休眠一段随机时间的方式模拟了下载和上传需要花费的时间,并没有联网做上传下载。
说明 :用Python语言实现联网的上传下载也很简单,继续你的学习,这个环节很快就会来到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import randomimport timedef download (filename) : print(f'开始下载{filename} .' ) time.sleep(random.randint(2 , 6 )) print(f'{filename} 下载完成.' ) def upload (filename) : print(f'开始上传{filename} .' ) time.sleep(random.randint(4 , 8 )) print(f'{filename} 上传完成.' ) download('MySQL从删库到跑路.avi' ) upload('Python从入门到住院.pdf' )
现在我们希望知道调用download
和upload
函数做文件上传下载到底用了多少时间,这个应该如何实现呢?相信很多小伙伴已经想到了,我们可以在函数开始执行的时候记录一个时间,在函数调用结束后记录一个时间,两个时间相减就可以计算出下载或上传的时间,代码如下所示。
1 2 3 4 5 6 7 8 start = time.time() download('MySQL从删库到跑路.avi' ) end = time.time() print(f'花费时间: {end - start:.3 f} 秒' ) start = time.time() upload('Python从入门到住院.pdf' ) end = time.time() print(f'花费时间: {end - start:.3 f} 秒' )
通过上面的代码,我们可以得到下载和上传花费的时间,但不知道大家是否注意到,上面记录时间、计算和显示执行时间的代码都是重复代码。有编程经验的人都知道,重复的代码是万恶之源 ,那么有没有办法在不写重复代码的前提下,用一种简单优雅的方式记录下函数的执行时间呢?在Python中,装饰器就是解决这类问题的最佳选择。我们可以把记录函数执行时间的功能封装到一个装饰器中,在有需要的地方直接使用这个装饰器就可以了。
二、普通装饰器 将上文示例中的代码进行一下修改,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import timedef record_time (func) : def wrapper (*args, **kwargs) : start = time.time() result = func(*args, **kwargs) end = time.time() print(f'{func.__name__} 执行时间: {end - start:.3 f} 秒' ) return result return wrapper
使用上面的装饰器函数有两种方式,第一种方式就是直接调用装饰器函数,传入被装饰的函数并获得返回值,我们可以用这个返回值直接覆盖原来的函数,那么在调用时就已经获得了装饰器提供的额外的功能(记录执行时间),大家可以试试下面的代码就明白了。
1 2 3 4 download = record_time(download) upload = record_time(upload) download('MySQL从删库到跑路.avi' ) upload('Python从入门到住院.pdf' )
上面的代码中已经没有重复代码了,虽然写装饰器会花费一些心思,但是这是一个一劳永逸的骚操作,如果还有其他的函数也需要记录执行时间,按照上面的代码如法炮制即可。
在Python中,使用装饰器很有更为便捷的语法糖 (编程语言中添加的某种语法,这种语法对语言的功能没有影响,但是使用更加方法,代码的可读性也更强),可以用@装饰器函数
将装饰器函数直接放在被装饰的函数上,效果跟上面的代码相同,下面是完整的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import randomimport timedef record_time (func) : def wrapper (*args, **kwargs) : start = time.time() result = func(*args, **kwargs) end = time.time() print(f'{func.__name__} 执行时间: {end - start:.3 f} 秒' ) return result return wrapper @record_time def download (filename) : print(f'开始下载{filename} .' ) time.sleep(random.randint(2 , 6 )) print(f'{filename} 下载完成.' ) @record_time def upload (filename) : print(f'开始上传{filename} .' ) time.sleep(random.randint(4 , 8 )) print(f'{filename} 上传完成.' ) download('MySQL从删库到跑路.avi' ) upload('Python从入门到住院.pdf' )
上面的代码,我们通过装饰器语法糖为download
和upload
函数添加了装饰器,这样调用download
和upload
函数时,会记录下函数的执行时间。事实上,被装饰后的download
和upload
函数是我们在装饰器record_time
中返回的wrapper
函数,调用它们其实就是在调用wrapper
函数,所以拥有了记录函数执行时间的功能。
三、多个装饰器 多个装饰器同时修饰一个函数的的情况你能想到他们的执行顺序是什么样子的吗?看看下面的代码你就知道了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def makeBold (fn) : def wrapped () : print("-----1-----" ) return "<b>" + fn() + "</b>" return wrapped def makeItalic (fn) : def wrapped () : print("-------2-----" ) return "<i>" + fn() + "</i>" return wrapped @makeBold # makeBold(makeItalic(test) @makeItalic # # makeItalic(test def test3 () : print("-------3------" ) return "hello world" test() """ -----1----- -------2----- -------3------ <b><i>hello world</i></b> """
从上面的代码可以看出,多个装饰器同时修饰一个函数时,内函数执行顺序是从上往下(从最外层到最内层)以此执行。下面再来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 def makeBold (fn) : print('进入 makeBold 装饰器 -1' ) def wrapped () : print("makeBold开始装饰 1" ) return "<b>" + fn() + "</b>" return wrapped def makeItalic (fn) : print('进入 makeItalic 装饰器 -2' ) def wrapped () : print("makeItalic开始装饰 2" ) return "<i>" + fn() + "</i>" return wrapped @makeBold @makeItalic def test () : print("执行test函数内容逻辑 - 3" ) return "hello world" print(test()) """ 进入 makeItalic 装饰器 -2 进入 makeBold 装饰器 -1 makeBold开始装饰 1 makeItalic开始装饰 2 执行test函数内容逻辑 - 3 <b><i>hello world</i></b> """
我们发现多个装饰器修饰一个函数时,多个装饰器外函数执行顺序和内函数执行顺序居然是相反的,外函数是从下往上(由内向外)顺序执行,这个是为什么呢?其实仔细想想,当Python解释器执行到@makeBold
这一行代码时,会把其转化为makeBold(makeItalic(test))
,两个装饰器函数会在这里执行,先会执行内层装饰器,后执行外层装饰器,而test()
这个过程相当于开始执行装饰器返回值内函数,执行顺序从外层装饰器到内层装饰器。即多个装饰器的外函数在Python解释器执行到对应代码时会执行,内函数会有被装饰函数执行(加括号)时执行,执行顺序是外函数由内向外执行,内涵数由外向内执行。
四、被装饰函数的参数 那么装饰器修饰被装饰函数时,被装饰函数的参数如何处理呢?我们分三种情况来分析。
情况1:被装饰函数无参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import timedef record_time (func) : def wrapper () : start_time = time.time() func() end_time = time.time() print('spend is {}' .format(end_time - start_time)) return wrapper @record_time # 得到闭包函数record_time, func = record_time(func) def func () : print('func do something!' ) time.sleep(1 ) func() """ func do something! spend is 1.0044233798980713 """
情况2:有指定个数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import timedef record_time (func) : def wrapper (a, b) : start_time = time.time() func(a, b) end_time = time.time() print('spend is {}' .format(end_time - start_time)) return wrapper @record_time # 得到闭包函数record_time, add = record_time(add) def add (a, b) : print(a + b) time.sleep(1 ) add(3 , 4 ) """ 7 spend is 1.0194220542907715 """
情况3:不定长参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import timedef record_time (func) : def wrapper (*args, **kwargs) : start_time = time.time() func(*args, **kwargs) end_time = time.time() print('spend is {}' .format(end_time - start_time)) return wrapper @record_time # 得到闭包函数record_time, add = record_time(add) def add (*args) : num_sum = sum(num for num in args) print(f'{args} sum is {num_sum} ' ) time.sleep(1 ) add(1 ,2 ,3 ) add(2 ,3 ,4 ,5 ) """ (1, 2, 3) sum is 6 spend is 1.0004258155822754 (2, 3, 4, 5) sum is 14 spend is 1.0004265308380127 """
通常情况下,推荐大家用第三种写法。
五、被装饰函数带有返回值 当被装饰函数带有返回值的情况如何处理呢?下面的例子告诉你答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def outer (outertionName) : print("-----outer---1----" ) def inner () : print("----inner---1--" ) ret = outertionName() print("---inner---2---" ) return ret print("----outer---2---" ) return inner @outer def func () : print("---func----" ) return "haha" ret = func() print(f"func return value is {ret} " ) """ -----outer---1---- ----outer---2--- ----inner---1-- ---func---- ---inner---2--- func return value is haha """
六、带有参数的装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import timedef time_logger (flag=0 ) : def record_time (func) : def wrapper (a, b) : start_time = time.time() func(a, b) end_time = time.time() print('spend is {}' .format(end_time - start_time)) if flag: print('将此操作保留至日志' ) return wrapper return record_time @time_logger(2) # 得到闭包函数record_time,add = record_time(add) def add (a, b) : print(a + b) time.sleep(1 ) add(3 , 4 ) """ 将此操作保留至日志 7 spend is 1.0813958644866943 """
七、类装饰器 1. 普通类装饰器 示例:记录一个函数的执行时间,用类装饰器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import timeclass RecordTime (object) : def __init__ (self, func) : self._func = func def __call__ (self) : start_time = time.time() self._func() end_time = time.time() print('spend is {}' .format(end_time - start_time)) @RecordTime # bar = RecordTime(bar) def func () : print('func..' ) time.sleep(2 ) func() """ func.. spend is 2.0018553733825684 """
2. 多个类装饰器 示例:在eat函数执行之前执行洗手,执行之后执行洗碗操作,用两个类装饰器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import timeclass WashHands (object) : def __init__ (self, func) : self._func = func def __call__ (self) : print('wash hands ...' ) return self._func() class WashDishes (object) : def __init__ (self, func) : self._func = func def __call__ (self) : ret = self._func() print('wash the dishes ...' ) return ret @WashHands @WashDishes def eat () : print('start eating ...' ) time.sleep(2 ) eat() """ wash hands ... start eating ... wash the dishes ... """
3. 带参数的类装饰器 类装饰器函数本身也可以参数化 ,简单的说就是通过我们的装饰器也是可以通过调用者传入的参数来定制的,这个知识点我们在后面用上它的时候再为大家讲解。除了可以用函数来定义装饰器之外,通过定义类的方式也可以定义装饰器。如果一个类中有名为__call__
的魔术方法,那么这个类的对象就可以像函数一样调用,这就意味着这个对象可以像装饰器一样工作,代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import randomimport timefrom functools import wrapsclass RecordTime : def __init__ (self, num=4 ) : self.num = num def __call__ (self, func) : @wraps(func) def wrapper (*args, **kwargs) : start = time.time() result = func(*args, **kwargs) end = time.time() print(f'{func.__name__} 执行时间: {round(end - start, self.num)} 秒' ) return result return wrapper @RecordTime(num=3) def download (filename) : print(f'开始下载{filename} .' ) time.sleep(random.randint(2 , 6 )) print(f'{filename} 下载完成.' ) def upload (filename) : print(f'开始上传{filename} .' ) time.sleep(random.randint(4 , 8 )) print(f'{filename} 上传完成.' ) upload = RecordTime()(upload) download('MySQL从删库到跑路.avi' ) upload('Python从入门到住院.pdf' ) """ 开始下载MySQL从删库到跑路.avi. MySQL从删库到跑路.avi下载完成. download执行时间: 2.001秒 开始上传Python从入门到住院.pdf. Python从入门到住院.pdf上传完成. upload执行时间: 5.0002秒 """
上面的代码演示了两种添加装饰器的方式,由于RecordTime
是一个类,所以需要先创建对象,才能把对象当成装饰器来使用,所以提醒大家注意RecordTime
后面的圆括号,那是调用构造器创建对象的语法。如果为RecordTime
类添加一个__init__
方法,就可以实现对装饰器的参数化。使用装饰器还可以装饰一个类,为其提供额外的功能,这个知识点也等我们用到的时候再做讲解。
八、装饰器作用在类上 除了可以用在方法上,其实python的装饰器也可以作用于类上,在不改变类的情况下,给类增加一些额外的功能.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def log_getattribute (cls) : origin_getattribute = cls.__getattribute__ def new_getattribute (self, name) : print('greeting:' , name) return origin_getattribute(self, name) cls.__getattribute__ = new_getattribute return cls @log_getattribute class A : def __init__ (self, x) : self.x = x def spam (self) : pass if __name__ == '__main__' : a = A('x' ) print(a.x) print(a.spam()) """ greeting: x x greeting: spam None """
九、保留原对象指定属性信息 以上书写装饰器的方式有什么问题吗?看下面一个例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import timedef record_time (func) : def wrapper (*args, **kwargs) : start_time = time.time() func(*args, **kwargs) end_time = time.time() print('spend is {}' .format(end_time - start_time)) return wrapper def func () : print('func ...' ) print('decorator before name is ' , func.__name__) @record_time def func () : print('func ...' ) print('decorator after name is ' , func.__name__) """ decorator before name is func decorator after name is wrapper """
由此可以看出,装饰器会对原函数的元信息进行更改,可以使用wraps,进行原函数信息的添加
注解:wraps本身也是一个装饰器,他能把函数的元信息拷贝到装饰器函数中使得装饰器函数与原函数有一样的元信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import timefrom functools import wrapsdef record_time (func) : @wraps(func) def wrapper (*args, **kwargs) : start_time = time.time() func(*args, **kwargs) end_time = time.time() print('spend is {}' .format(end_time - start_time)) return wrapper def func () : print('func ...' ) print('decorator before name is ' , func.__name__) @record_time def func () : print('func ...' ) print('decorator after name is ' , func.__name__) """ decorator before name is func decorator after name is func """
十、取消装饰器的作用 如果希望取消装饰器的作用,那么在定义装饰器函数的时候,需要做一些额外的工作。Python标准库functools
模块的wraps
函数也是一个装饰器,我们将它放在wrapper
函数上,这个装饰器可以帮我们保留被装饰之前的函数,这样在需要取消装饰器时,可以通过被装饰函数的__wrapped__
属性获得被装饰之前的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import randomimport timefrom functools import wrapsdef record_time (func) : @wraps(func) def wrapper (*args, **kwargs) : start = time.time() result = func(*args, **kwargs) end = time.time() print(f'{func.__name__} 执行时间: {end - start:.3 f} 秒' ) return result return wrapper @record_time def download (filename) : print(f'开始下载{filename} .' ) time.sleep(random.randint(2 , 6 )) print(f'{filename} 下载完成.' ) @record_time def upload (filename) : print(f'开始上传{filename} .' ) time.sleep(random.randint(4 , 8 )) print(f'{filename} 上传完成.' ) download('MySQL从删库到跑路.avi' ) upload('Python从入门到住院.pdf' ) download.__wrapped__('MySQL必知必会.pdf' ) upload = upload.__wrapped__ upload('Python从新手到大师.pdf' )
十一、常用的内置装饰器 1.property @property 可以把一个实例方法变成其同名属性,以支持实例访问,它返回的是一个property属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import mathclass Circle : def __init__ (self, radius) : self.radius = radius @property def area (self) : return math.pi * self.radius ** 2 @property def perimeter (self) : return 2 * math.pi * self.radius circle = Circle(10 ) print(circle.radius) print(circle.area) print(circle.perimeter) """ 10 314.1592653589793 62.83185307179586 """
一个property对象还具有setter
、deleter
可用作装饰器;setter
是设置属性值。deleter
用于删除属性值。而官方文档中给出了getter用于获取属性信息,但是实际使用中可以直接通过property获取属性信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class C : def __init__ (self) : self._x = None @property def x (self) : return self._x @x.setter def x (self, value) : self._x = value @x.deleter def x (self) : del self._x c = C() c.x = 100 print(c.x) del c.x
总结:使用property:在设置属性时,可以对值对进检查,设置发生时,可以 修改设置的值,获取属性时,可以动态地计算值。
2.classmethod 修饰的方法不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class A () : number = 10 @classmethod def get_a (cls) : print('这是类本身:' ,cls) print('这是类属性:' ,cls.number) class B (A) : number = 20 pass A.get_a() B.get_a() """ 这是类本身: <class '__main__.A'> 这是类属性: 10 这是类本身: <class '__main__.B'> 这是类属性: 20 """
3.staticmethod @staticmethod
:改变一个方法为静态方法,静态方法不需要传递隐性的第一参数,静态方法的本质类型就是一个函数 一个静态方法可以直接通过类进行调用,也可以通过实例进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import timeclass Date : def __init__ (self, year, month, day) : self.year = year self.month = month self.day = day @staticmethod def now () : t = time.localtime() return Date(t.tm_year, t.tm_mon, t.tm_mday) @staticmethod def tomorrow () : t = time.localtime(time.time() + 86400 ) return Date(t.tm_year, t.tm_mon, t.tm_mday) a = Date('1987' , 11 , 27 ) print(a.year, a.month, a.day) b = Date.now() print(b.year, b.month, b.day) c = Date.tomorrow() print(c.year, c.month, c.day) """ 1987 11 27 2020 12 17 2020 12 18 """
十二、简单的总结 闭包和装饰器是Python中的特色语法,装饰器是闭包可以通过装饰器来增强现有的类或函数,这是一种非常有用的编程技巧。