认识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()

这里的@,我们称之为语法糖,@my_zhuangsq就相当于前面的say_hello = my_zhuangsq(say_hello)语句,只不过更加简洁。因此,如果你的程序中有其它函数需要做类似的装饰,你只需在它们的上方加上@decorator就可以了,这样就大大提高了函数的重复利用和程序的可读性。

提问:什么是装饰器、装饰器的作用时什么呢?

带有参数的装饰器

如果原函数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、输入合理性检查

  •  

posted @ 2020-06-19 14:53  风车车与车车风  阅读(182)  评论(0)    收藏  举报