万物皆对象
介绍装饰器之前,我们需要理解一个概念:在介绍装饰器前,我们需要理解一个概念:在 Python 开发中,一切皆对象。
什么意思呢?
就是我们在开发中,无论是定义的变量(数字、字符串、元组、列表、字典)、还是方法、类、实例、模块,这些都可以称作对象。
怎么理解呢?在 Python 中,所有的对象都会有属性和方法,也就是说可以通过「.」去获取它的属性或调用它的方法,例如像下面这样:
i = 10 # int对象 print(id(i)) # 获取i的内存地址 print(type(i)) # 获取i的类型 s = 'hello' # str 对象 print(id(s)) # 获取s的内存地址 print(type(s)) # 获取s的类型 def hello(): # function对象 print ('Hello World') print (id(hello)), print(type(hello)), print(hello.__name__) hello2 = hello print (id(hello2)), print(type(hello2)), print(hello2.__name__) out: 140704515711920 <class 'int'> 2637870361712 <class 'str'> 2637873499536 <class 'function'> hello 2637873499536 <class 'function'> hello
我们可以看到,常见的这些类型:int
、str
、dict
、function
,甚至 class
、instance
都可以调用 id
和 type
获得对象的唯一标识和类型。
例如方法的类型是 function
,类的类型是 type
,并且这些对象都是可传递的。
方法对象可传递会带来什么好处呢?
这么做的好处就是,我们可以实现一个「闭包」,而「闭包」就是实现一个装饰器的基础。
闭包
闭包的概念:概念:函数里面定义函数
闭包的三个条件:
1.函数中嵌套一个函数
2.外层函数返回内层函数的变量名
3.内层函数对外部作用域有一个非全局变量进行引用
闭包的作用:提高稳定性,实现数据的锁定
闭包举例:
假设我们现在想统计一个方法的执行时间,通常实现的逻辑如下:
*# coding: utf8* import time def hello(): start = time.time() *# 开始时间* time.sleep(1) *# 模拟执行耗时* print 'hello' end = time.time() *# 结束时间* print 'duration time: %ds' % int(end - start) *# 计算耗时* hello() *# Output:* *# hello* *# duration time: 1s*
统计一个方法执行时间的逻辑很简单,只需要在调用这个方法的前后,增加时间的记录就可以了。
但是,统计这一个方法的执行时间这么写一次还好,如果我们想统计任意一个方法的执行时间,每个方法都这么写,就会有大量的重复代码,而且不宜维护。
如何解决?这时我们通常会想到,可以把这个逻辑抽离出来:
`import time` `def timeit(func): *# 计算方法耗时的通用方法*` `start = time.time()` `func() *# 执行方法*` `end = time.time()` `print 'duration time: %ds' % int(end - start)` `def hello():` `time.sleep(1)` `print 'hello'` `timeit(hello) *# 调用执行*`
这里我们定义了一个 timeit
方法,而参数传入一个方法对象,在执行完真正的方法逻辑后,计算其运行时间。
这样,如果我们想计算哪个方法的执行时间,都按照此方式调用即可。
timeit(func1) *# 计算func1执行时间*
timeit(func2) *# 计算func2执行时间*
虽然此方式可以满足我们的需求,但有没有觉得,本来我们想要执行的是 hello
方法,现在执行都需要使用 timeit
然后传入 hello
才能达到要求,有没有一种方式,既可以给原来的方法加上计算时间的逻辑,还能像调用原方法一样使用呢?
答案当然是可以的,我们对 timeit
进行改造:
*`# coding: utf8*` `import time` `def timeit(func):` `def inner():` `start = time.time()` `func()` `end = time.time()` `print 'duration time: %ds' % int(end - start)` `return inner` `def hello():` `time.sleep(1)` `print 'hello'` `hello = timeit(hello) *# 重新定义hello*` `hello() *# 像调用原始方法一样使用*`
注意观察timeit
的变动,它在内部定义了一个inner
方法,此方法内部的实现与之前类似,但是,timeit
最终返回的不是一个值,而是inner
对象。所以当我们调用hello = timeit(hello)
时,会得到一个方法对象,那么此时变量hello
其实是inner
,在执行hello()
时,真正执行的是inner
方法。我们对hello
方法进行了重新定义,这么一来,hello
不仅保留了其原有的逻辑,而且还增加了计算方法执行耗时的新功能。回过头来,我们分析一下timeit
这个方法是如何运行的?在 Python 中允许在一个方法中嵌套另一个方法,这种特殊的机制就叫做「闭包」,这个内部方法可以保留外部方法的作用域,尽管外部方法不是全局的,内部方法也可以访问到外部方法的参数和变量。
装饰器
明白了闭包的工作机制后,那么实现一个装饰器就变得非常简单了。Python 支持一种装饰器语法糖「@」,使用这个语法糖,我们也可以实现与上面完全相同的功能:
# coding: utf8 @timeit # 相当于 hello = timeit(hello) def hello(): time.sleep(1) print 'hello' hello() # 直接调用原方法即可
看到这里,是不是觉得很简单?这里的@timeit
其实就等价于hello = timeit(hello)
。装饰器本质上就是实现一个闭包,把一个方法对象当做参数,传入到另一个方法中,然后这个方法返回了一个增强功能的方法对象。这就是装饰器的核心,平时我们开发中常见的装饰器,无非就是这种形式的变形而已。
装饰器原理:
1、装饰器可以实现在不更改原有代码的前提下扩展新的功能
2、开放封闭原则:已实现的功能可扩展,但不能修改
实现一个简单装饰器:
# 实现一个简单装饰器 def func_a(fun): print("func_a调用了") def func_b(): print("先洗手") fun() return func_b @func_a def func_demo(): print("欢迎进入首页") func_demo() #func_a调用了 #先洗手 #欢迎进入首页
带参数的装饰器:
def add(func): def fun(a,b): print('相乘',a*b) print('相除',a/b) func(a,b) return fun @add def add_num(a,b): print("相加",a+b) (add_num(2,4)) # 相乘 8 # 相除 0.5 # 相加 6
通用装饰器
def add(func): def fun(*args,**kwargs): print("装饰器的功能代码:登录") func(*args,**kwargs) return fun @add def index(): print("这是网站首页") @add def goods_list(num): print("这是商品列表第{}页".format(num)) a = index a() b = goods_list b(9) # 装饰器的功能代码:登录 # 这是网站首页 # 装饰器的功能代码:登录 # 这是商品列表第9页
类装饰器
def add(func): def fun(*args, **kwargs): print("装饰器的功能代码:登录") return func(*args, **kwargs) # 类级别一定要有return,否则会返回None return fun @add class Myclass: def __init__(self, name, pw): self.name = name self.pw = pw m = Myclass('xiucai', 123) print(m.pw) print(m.name) # 装饰器的功能代码:登录 # 123 # xiucai
以上文章部分参考:https://zhuanlan.zhihu.com/p/425626332