引子
在 Python 中有一个非常重要且有特色的语法 修饰器 ,但是修饰器的语法也非常难懂,只有更好的理解修饰器原理才能更好掌握该语法。在网上查了不少资料,但是发现很多文章仅仅介绍了修饰器的基本使用而没有介绍其 原理 ,本文将分从 基本语法 和 实现原理 两个方面谈谈我对修饰器的理解。
基本语法
无参修饰器
1 2 3 4 5 6 7 8 9 10 11 12 def decorator (func ): def wrapper (*args, **kwargs ): __ = func(*args, **kwargs) return __ return wrapper @decorator def func (*args, **kwargs ): pass
下面举个时间测试修饰器的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import timedef time_it (func ): def wrapper (*args, **kwargs ): _start_time = time.time() __ = func(*args, **kwargs) _end_time = time.time() print ("time_it: {}" .format (end_time - start_time)) return __ return wrapper @time_it def test (sec ): time.sleep(sec) if __name__ == "__main__" : test()
由此也可见,Python 修饰器最大的优势就是可以方便的将代码放入一个嵌套的逻辑中,而如果我们每次需要编写这个嵌套逻辑往往很繁琐的。
带参修饰器
带参修饰器需要在无参修饰器外面再套一层壳,是不是感觉很奇怪?感觉这套语法怎么这么多嵌套?别急,我们后面会解释的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def decorator (*args, **kwargs ): def inner (func ): def wrapper (*args_, **kwargs_ ): __ = func(*args_, **kwargs_) return __ return wrapper return inner @decorator(*args, **kwargs ) def func (*args, **kwargs ): pass
下面举个互斥锁的例子,这样 原子操作 抽象为函数后只需简单添加修饰器即可。
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 import threadingticket_id = 1 mutex = threading.Lock() def WithMutex (mutex: threading.Lock ): def decorator (func ): def wrapper (*args, **kwargs ): mutex.acquire() __ = func(*args, **kwargs) mutex.release() return __ return wrapper return decorator @WithMutex(mutex ) def atom (i: int ) -> bool : global ticket_id if ticket_id > 100 : return False print ("Thread {} give out ticket {}" .format (i, ticket_id)) ticket_id += 1 return True def run (i: int ): while atom(i): pass if __name__ == "__main__" : threads = [] for i in range (5 ): threads.append(threading.Thread(target=run, args=(i, ))) for i in range (5 ): threads[i].start() for i in range (5 ): threads[i].join()
实现原理
感觉很抽象,非常的难记。但是解释一下原理你就懂了。
其实 无参修饰器 和 带参修饰器 在原理上是一致的,那么解释器是如何解释这两段代码的呢?可以看到不管是 无参修饰器 还是 带参修饰器 它们都有共同的特点,返回一系列的函数。其实解释器是这样解释的:
1 2 3 4 5 6 @decorator def func (*args, **kwargs ): pass func(*args, **kwargs)
1 2 3 4 5 decorator(func)(*args, **kwargs)
我们来仔细理解一下这份代码。decorator(func)
返回了一个内嵌函数 wrapper
(函数也是对象,请区别函数与函数返回值),而内嵌函数实际获取了参数 *args, **kwargs
。这里还值得一提的是,内嵌函数的实现允许其保留外部函数的变量,通常外部函数生命周期结束后其局部变量都将释放,而特别的是如果存在内嵌函数被保留,外部函数的局部变量不会释放。在这个示例中,func
以参数的形式传入外部函数 decorator
,而在内嵌函数 wrapper
中依然可用。
这样我们也可以很好解释 带参修饰器 ,就是多嵌套了一层函数:
1 2 3 4 5 6 @decorator(*args, **kwargs ) def func (*args_, **kwargs_ ): pass func(*args_, **kwargs_)
1 2 decorator(*args, **kwargs)(func)(*args_, **kwargs_)
我个人对于修饰器的一些吐槽 尽管 Python 修饰器的语法在频繁调用 “修饰” 功能时很方便,但不可否认实现一个修饰器的代码非常麻烦而且难读。
虽然实现修饰器的代码逻辑非常符合其实现原理,但 Python 完全可以使用一套更方便的语法实现修饰器。