本系列目的:但愿能够经过一篇文章,不望鞭辟入里,但求在工程应用中驾轻就熟。python
包含八节内容:闭包(实现装饰器的基础),不带参数的函数装饰器,带参数的函数装饰器,不带参数的类装饰器,带参数的类装饰器,经常使用内建装饰器,装饰器总结(套路总结),装饰器经典实例(单例模式)。git
闭包github
装饰器是经过闭包实现的。闭包是一个比较复杂的话题,深了说能够讲到python对常量表和符号表的处理方式。这里只作简单介绍。我的认为只要记住如下三个特性,就明白了闭包的概念。设计模式
不带参数函数装饰器
假设有一个需求,咱们须要在每一个函数运行时,打印当下时刻的时间戳。那么有如下两种写法:安全
不使用装饰器
编写一个打印时间戳的工具函数,编写一个业务函数。传入业务函数对象到工具函数中,实现打印时间戳并执行业务函数的需求。代码以下:多线程
import time def f(): print("f is running!") def f1(): print("f1 is running!") def print_running_time(f): print("running time:", time.time()) f() print_running_time(f) print_running_time(f1) >>> ('running time:', 1588864281.154459) f is running! ('running time:', 1588864281.154483) f1 is running!
使用装饰器(函数装饰器)闭包
编写一个打印时间戳的装饰器函数,编写一个业务函数。装饰器函数装饰业务函数,实现打印时间戳并执行业务函数的需求。代码以下:app
import time def print_running_time(f): def wrapper(): print("running time:", time.time()) f() return wrapper @print_running_time def f(): print("f is running!") @print_running_time def f1(): print("f1 is running!") f() f1() >>> ('running time:', 1588864281.154459) f is running! ('running time:', 1588864281.154483) f1 is running!
以上两种写法对比
经过对比以上两种写法,咱们能够发现最明显的区别是代码在运行时,第一种写法执行的print_running_time函数,第二种写法执行的是f函数。那么明显第二种写法中抽象出的语义更加接近咱们的业务需求。在一样需求增长的状况下,第一种写法须要写更多的工具函数,而且在执行业务函数时须要进行多层嵌套,极大地增长了代码的复杂度。第二种写法能够增长多个装饰函数装饰到业务函数上方,在多需求下依旧保持代码的可读性和层次感,功能的独立性和扩展性。函数
初探装饰器原理工具
装饰器的代码运行分为两步,装饰器初始化(在运行至被装饰函数定义处)和执行被装饰函数(在运行至被装饰函数调用处)
以第二种写法装饰器的写法为例,装饰器的原理以下:
在代码加载过程当中,代码从上往下执行,那么在执行到#1代码时,至关于执行了#2代码。(#1和#2的代码是等价的。@docorator_func装饰f,就至关于执行decorator_func(f))。根据#2代码中print_running_time可知,执行print_running_time(f)的返回值是wrapper(注意返回的是函数对象wrapper,不是wrapper()).
# 1 @print_running_time def f(): print("f is running!") # 2 print_running_time(f) # 3 def print_running_time(f): #3.1 def wrapper(): #3.2 print("running time:", time.time()) #3.3 f() #3.4 return wrapper # 4 f()
那么源码中#1处的三行代码,返回值为wrapper,即至关于经过增长@装饰函数,f如今已经指向了wrapper对象。
根据以前提到闭包的特性:闭包能够访问做用以外的非局部变量,能够将做用域"封装",在闭包以外访问闭包内的变量。因此wrapper能够访问#3.1中到本身外层函数的参数f变量(被装饰器函数对象),而且能够封装wrapper做用域,保存f变量。
执行#4处的业务函数f(),即执行#3.2的wrapper()代码,即执行#3.3和#3.4代码。
整个过程当中须要注意的是,在代码运行至#1时,f做为装饰器参数被#3.2wrapper闭包保留,在#1执行完以后,会存在两个f对象,#4的f对象指向wrapper,#3.4的f对象依旧是#1处的f对象。
执行流程为f()> wrapper()> 执行#7.1 #7.2代码==>打印当前时刻时间戳,顺利执行了原有的业务函数。
带参数的函数装饰器
如今有新的需求,根据调试和生产环境的不一样,须要往复地开关打印时间戳的功能,那么这时就须要为装饰器函数增长参数,来做为是否打印时间戳的开关。如如下代码所示,f()会打印当前函数的执行时间,f1()则不会打印函数的执行时间
import time # 1 def print_running_time(*flag): def outer_wrapper(f): def inner_wrapper(): if flag: print("running time:", time.time()) f() return inner_wrapper return outer_wrapper # 2 @print_running_time(1) def f(): print("f is running!") # 3 @print_running_time() def f1(): print("f1 is running!") f() f1() >>> ('running time:', 1588860065.265516) f is running! f1 is running!
带参数装饰器原理
不带参数类装饰器
准确来讲,装饰器的本质是将一个可调用对象做为参数传入另外一个可调用对象,而后经过闭包保存变量,在适当的时候执行。咱们知道,python有两个特性
根据装饰器的本质和以上Python两个特性能够得出如下结论:
将以前简单函数装饰器的例子换成类装饰器,代码以下(为了与以前代码保持一致,因此类名不符合Python命名规范):
import time class print_running_time: def __init__(self, f): # 至关于闭包,经过实例属性保存变量f实现闭包中的变量封装 self.f = f def __call__(self): # 类实例能够被调用 print("running time:", time.time()) return self.f() @print_running_time def f(): print("f is running!") @print_running_time def f1(): print("f1 is running!") f() f1() >>> 输出同简单函数装饰器
带参数的类装饰器
将以前简单函数装饰器的例子换成类装饰器,代码以下:
import time class print_running_time: def __init__(self, *flag): # 至关于闭包,经过实例属性保存变量flag实现闭包中的变量封装 self.flag = flag def __call__(self, f): # 类实例能够被调用,传入业务函数f def wrapper(): if self.flag: print("running time:", time.time()) f() return wrapper @print_running_time(1) def f(): print("f is running!") @print_running_time() def f1(): print("f1 is running!") f() f1() >>> 输出同带参数的函数装饰器
经常使用内建装饰器
装饰器是Python最重要的特性之一,Python实现了不少对装饰器的支持
wraps
wraps能够保留被装饰函数的__doc__。以下代码所示,wraps装饰器的开关会致使打印f.__doc__出现两种结果
import time from functools import wraps # 1 def print_running_time(f): @wraps(f) # 1.1 def wrapper(): '''the func wrapper''' # 1.3 print("running time:", time.time()) f() return wrapper # 2 @print_running_time def f(): '''the func f''' # 2.1 print("f is running!") print(f.__doc__) >>> the func f
property 、setter、 deleter
这三个是孪生兄弟,其中property用的最多,setter和deleter依附property。
class Student(object): @property def name(self): return self._name @name.setter def name(self, name): if len(name) < 2: raise ValueError("无名大侠?") self._name = name @name.deleter def name(self): del self._name stu = Student() stu.name = "刘" # name.setter print(stu.name) ## property del stu.name # name.deleter print(stu.name) # raise AttributeError
多装饰器叠加
多个装饰器叠加是python中很常见的骚操做,如Flask和Django中都会用到,举例以下:
import sys # 1 def f1(func): print('f1 start') def wrapper(): # 1.1 print('f1 ' + sys._getframe().f_code.co_name + ' start') func() # 1.2 print('f1 ' + sys._getframe().f_code.co_name + ' end') print('f1 end') return wrapper # 2 def f2(func): print('f2 start') def wrapper(): # 2.1 print('f2 ' + sys._getframe().f_code.co_name + ' start') func() # 2.2 print('f2 ' + sys._getframe().f_code.co_name + ' end') print('f2 end') return wrapper # 3 @f1 @f2 def func(): #3.1 print('the func') #4 func() #4.1 >>> f2 start f2 end f1 start f1 end f1wrapper start f2wrapper start the func f2wrapper end f1wrapper end
>>> f2 start f2 end f1 start f1 end
print('f1 ' + sys._getframe().f_code.co_name + ' start') func() # 1.2 print('f1 ' + sys._getframe().f_code.co_name + ' end')#1.2处的func <=> # 2.1处的wrapper,替换以后代码以下
print('f1 ' + sys._getframe().f_code.co_name + ' start') print('f2 ' + sys._getframe().f_code.co_name + ' start') func() # 2.2 print('f2 ' + sys._getframe().f_code.co_name + ' end') print('f1 ' + sys._getframe().f_code.co_name + ' end')#2.2处的func即为#4.1处的func,执行以上代码,输出结果以下:
>>> f1wrapper start f2wrapper start the func f2wrapper end f1wrapper end
装饰器总结
# 1 @decorator_func def func(): pass # 2 decorator_func(func)
外层函数参数为被装饰函数对象
内层参数为被装饰函数的参数
带参数的函数装饰器须要有三层函数:
类装饰器同理,最外层函数能够用__init_函数代替,中层(若是有和内层函数写在__call__中
装饰器经典实例:单例模式
如下均单进程可行,多线程须要加锁
单例模式
```python # eg:1 class Singleton: _singleton = None def __new__(cls): if cls._singleton is None: cls._singleton = super().__new__(cls) return cls._singleton ins1 = Singleton() ins2 = Singleton() print(ins1 is ins2) # eg:2 def singleton(cls): ins_pool = {} def inner(): if cls not in ins_pool: ins_pool[cls] = cls() return ins_pool[cls] return inner @singleton class Cls: def __init__(self): pass ins1 = Cls() ins2 = Cls() print(ins1 is ins2) # eg:3 class Singleton: def __init__(self, cls): self.ins_pool = {} self.cls = cls def __call__(self): print(self.ins_pool) if self.cls not in self.ins_pool: self.ins_pool[self.cls] = self.cls() return self.ins_pool[self.cls] @Singleton class Cls: def __init__(self): pass ins1 = Cls() ins2 = Cls() print(ins1 is ins2) ```
但愿你们能够经过本文掌握装饰器这个杀手级特性。欢迎关注我的博客:药少敏的博客