认识python的装饰器
认识装饰器
我们先看一个简单的例子:
def my_zhuangsq(func):
def new_fun():
print('帮助认识装饰器的作用')
func()
return new_fun
def say_hello():
print('hello world')
say_hello = my_zhuangsq(say_hello)
say_hello()
# 输出
帮助认识装饰器的作用
hello world
这段代码中、变量say_hello指向了内部函数new_fun(),而内部函数new_fun()又会调用原函数say_hello,所以最后调用say_hello()时、就会先打印“帮助认识装饰器的作用”,再打印“hello world”。这里的函数 my_zhuangsq() 就是一个装饰器,它把真正需要执行的函数 say_hello() 包裹在其中,并且改变了它的行为,但是原函数 say_hello() 不变。所以上面的代码还可以写的更加简洁一些:
def my_zhuangsq(func):
def new_fun():
print('帮助认识装饰器的作用')
func()
return new_fun
@my_zhuangsq
def say_hello():
print('hello world')
say_hello()
提问:什么是装饰器、装饰器的作用时什么呢?
带有参数的装饰器
如果原函数say_hello()中、有参数需要传递给装饰器该怎么办呢?一个简单的办法,是可以在对应的装饰器函数 new_fun() 上,加上相应的参数,比如:
def my_zhuangsq(func):
def new_fun(name):
print('帮助认识装饰器的作用')
func(name)
return new_fun
@my_zhuangsq
def say_hello(name):
print('hello {}'.format(name))
say_hello('xiaoming')
不过,新的问题来了。如果我另外还有一个函数,也需要使用 my_zhuangsq() 装饰器,但是这个新的函数有两个参数,又该怎么办呢?比如:
@my_zhuangsq
def say_hello(name1, name2):
print('hello {}'.format(name1))
print('hello {}'.format(name2))
通常情况下,我们会把"args"和"**kwargs",作为装饰器内部函数 new_fun() 的参数。"args"和"**kwargs",表示接受任意数量和类型的参数,因此装饰器就可以写成下面的形式:
def my_zhuangsq(func):
def new_fun(*args, **kwargs):
print('帮助认识装饰器的作用')
func(*args, **kwargs)
return new_fun
带有自定义参数的装饰器
其实,装饰器还有更大程度的灵活性。刚刚说了,装饰器可以接受原函数任意类型和数量的参数,除此之外,它还可以接受自己定义的参数。举个例子,比如我想要定义一个参数,来表示装饰器内部函数被执行的次数,那么就可以写成下面这种形式:
def counters(num):
def my_zhuangsq(func):
def new_fun(*args, **kwargs):
for i in range(num)
print('帮助认识装饰器的作用')
func(*args, **kwargs)
return new_fun
return my_zhuangsq
@counters(3)
def say_hello(name1, name2):
print('hello {}'.format(name1))
print('hello {}'.format(name2))
say_hello('xiaoming', 'xiaohong')
使用装饰器过后原函数的变化
我们使用上面的例子、来看一下使用装饰器过后、原函数的变化。
print(say_hello.__name__) "输出" 'my_zhuangsq'
可以看到函数的原信息已经被修改了,元信息告诉我们“它不再是以前的那个 say_hello() 函数,而是被 my_zhuangsq() 函数取代了”。为了解决这个问题,我们通常使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)。
import functools
def counters(num):
def my_zhuangsq(func):
@functools.wraps(func)
def new_fun(*args, **kwargs):
for i in range(num):
print('帮助认识装饰器的作用')
func(*args, **kwargs)
return new_fun
return my_zhuangsq
@counters(3)
def say_hello(name1, name2):
print('hello {}'.format(name1))
print('hello {}'.format(name2))
say_hello('xiaoming', 'xiaohong')
print(say_hello.__name__)
类装饰器
前面我们主要讲了函数作为装饰器的用法,实际上,类也可以作为装饰器。类装饰器主要依赖于函数"_call_()",每当你调用一个类的示例时,函数_call_()就会被执行一次。
class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
# 输出
num of calls is: 1
hello world
example()
# 输出
num of calls is: 2
hello world
...
这里,我们定义了类 Count,初始化时传入原函数 func(),而_call_()函数表示让变量 num_calls 自增 1,然后打印,并且调用原函数。因此,在我们第一次调用函数 example() 时,num_calls 的值是 1,而在第二次调用时,它的值变成了 2。
饰器本质上和函数装饰器原理、作用相同,都是为其它函数增加额外的功能。但是相比于函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器可以直接依靠类内部的_call_方法来实现,当使用 @ 形式将类装饰器附加到函数上时,就会调用类装饰器的_call_方法。而不需要向函数装饰器那样,在装饰器函数中定义嵌套函数,来实现装饰功能。
装饰器的嵌套
回顾刚刚讲的例子,基本都是一个装饰器的情况,但实际上,Python 也支持多个装饰器,比如写成下面这样的形式:
@zhuangsq3
@zhuangsq2
@zhuangsq1
def func():
...
它的执行顺序从里到外,所以上面的语句也等效于下面这行代码:
zhuangsq1(zhuangsq1(zhuangsq1(func)))
举个栗子:
import functools
def zhuangsq1(func):
@functools.wraps(func)
def new_fun(*args, **kwargs):
print('装饰器1__')
func(*args, **kwargs)
return new_fun
def zhuangsq2(func):
@functools.wraps(func)
def new_fun(*args, **kwargs):
print('装饰器2__')
func(*args, **kwargs)
return new_fun
@zhuangsq1
@zhuangsq2
def say_hello(name1, name2):
print('hello {}'.format(name1))
print('hello {}'.format(name2))
say_hello('xiaoming', 'xiaohong')
装饰器的应用
A、其实对于我们测试来说、自己写装饰器的应用场景并不多、现阶段其实能力也不太够、我们更需要知道的是很多应景封装好的装饰器该怎样使用!比如公司以后推的 pytest+selenium+allure 的自动化框架、pytest应用全是基于装饰器的、使用过程中就是命令行与装饰器的结合!
提问:说出两个我们遇到过的一些常用的装饰器!
B、在实际项目中、装饰器主要运用以下几个业务场景:(有兴趣的小伙伴自己查询资料)
-
1、身份验证
-
2、日志记录
-
3、输入合理性检查
-

浙公网安备 33010602011771号