装饰器

  • 装饰器(Decorator)的作用

python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能

它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

  • 什么是装饰器?下面我们取个例子:
import time
def func1():
    print('func1 is running')
    start = time.time()
    ### 代码块
    a = 10
    for i in range(1000000):
        a *= a
        a /= a
    end = time.time()
    print('func1 ends!')
    print('func1 run {} s!'.format(end-start))

def func2():
    print('func2 is running')
    start = time.time()
    ### 代码块
    a = []
    for i in range(1000000):
        a.append(i)
    end = time.time()
    print('func2 ends!')
    print('func2 run {} s!'.format(end-start))
func1()
func2()

## 输出
func1 is running
func1 ends!
func1 run 0.21602988243103027 s!
func2 is running
func2 ends!
func2 run 0.24808359146118164 s!

假设现有两个函数 func1 和 func2 ,我们需要打印这两个函数运行和结束的语句以及统计函数的运行时间,我们通常在这两个函数里面加入打印的语句。但是如果有很多这样的函数需要打印时,这种方法实在太累赘麻烦了,而且代码块部分才是每个函数的核心,没必要这么加入无关的代码。这时候,函数装饰器的作用就体现了。如下

import time
def log(func):
    print('{} is running'.format(func.__name__))
    start = time.time()
    func()
    end = time.time()
    print('{} ends!'.format(func.__name__))
    print('{} run {} s!'.format(func.__name__,end-start))

@log
def func1():
    ### 代码块
    a = 10
    for i in range(1000000):
        a *= a
        a /= a

@log
def func2():
    ### 代码块
    a = []
    for i in range(1000000):
        a.append(i)

## 输出
func1 is running
func1 ends!
func1 run 0.22405648231506348 s!
func2 is running
func2 ends!
func2 run 0.24805998802185059 s!

从上面可以看出,加入了@函数后,函数 func1 和 func2 就只需输入自己的核心代码部分了。

函数装饰器详解

我们先来看一段代码

import time
def decorator(func):
    start = time.time()
    func()
    end = time.time()
    print(end-start)

@decorator
def test():
    time.sleep(2)
    print('Hello World!')

## 输出
Hello World!
2.0020856857299805

>> test
None

在这段代码中,函数装饰器的作用实际上就是 test = decorator(test),因为 decorator 函数没有返回值,所以当输入 test 后,结果为 None

接着,我们再看一段代码:

import time
def decorator(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(end-start)
    return wrapper

@decorator
def test():
    time.sleep(2)
    print('Hello World!')

## 没有任何输出
## 因为 test = decorator(test) 返回一个 wrapper 函数

## 如果再加入下面代码
test()
## 则输出
Hello World!
2.0020856857299805

>> test() ## wrapper()函数没有返回值
None

上面两段代码都是无参数的函数装饰器,如果 test() 函数存在参数呢,那该怎么实现装饰器?

import time
def decorator(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        end = time.time()
        print(end-start)
    return wrapper

@decorator
def test(a,b):
    time.sleep(2)
    print(a+b)

test(1,2)

## 输出
3
2.0041680335998535

解释:test = decorator(test) = wrapper (test函数作为decorator函数的内变量),当输入 test(1,2) 时,等效于 wrapper(1,2) ,即输出该结果。

如果装饰器含有参数呢,该如何实现?(Flask 框架 route路由函数就存在装饰器含有参数,来标记装饰的路径。实际上,在装饰器函数里面再嵌套一个函数即可实现。如下

import time
def decorator(a):
    def out_wrapper(func):
        def wrapper(*args, **kwargs):
            if a == 'task1':
                starttime = time.time()
                func(*args, **kwargs)
                stoptime = time.time()
                print("the task1 run time is :", stoptime - starttime)
            elif a == 'task2':
                starttime = time.time()
                func(*args, **kwargs)
                stoptime = time.time()
                print("the task2 run time is :", stoptime - starttime)
        return wrapper
    return out_wrapper

@decorator(a ='task1')
def task1():
    time.sleep(1)
    print("in the task1")

@decorator(a ='task2')
def task2():
    time.sleep(1)
    print("in the task2")

task1()
task2()
  • 存在多个装饰器时
def dec1(func):
    print('1')
    def one():
        print('2')
        func()
        print('3')
    print('4')
    return one

def dec2(func):
    print('a')
    def two():
        print('b')
        func()
        print('c')
    print('d')
    return two

@dec1
@dec2
def test():
    print("test ------------")

test()

# test() = dec1(dec2(test))() = dec1(two)() 此时输出 'a','d'
#        = one() 此时输出 '1','4'
#             输出 '2', func() ---> two() 输出 'b', func() ---> test() 输出 'test --------','c' '3'

# 输出
'a' 'd' '1' '4' '2' 'b' 'test --------' 'c' '3'

之前的装饰器笔记版本

python 中@函数装饰器的工作原理。该部分参考来源

假设用 $funA()$ 函数装饰器去装饰 $funB()$ 函数,如下所示:

#funA 作为装饰器函数
def funA(fn):
    代码...   
    fn() # 执行传入的fn参数
    代码...    
    return '...'

@funA
def funB():
    代码

实际上,上面程序完全等价于下面的程序:

def funA(fn):
    代码...
    fn() # 执行传入的fn参数
    代码...    
    return '...'

def funB():
    代码...

funB = funA(funB)

通过比对以上 2 段程序不难发现,使用函数装饰器 A() 去装饰另一个函数 B(),其底层执行了如下 2 步操作:

  1. 将 B 作为参数传给 A() 函数
  2. 将 A() 函数执行完成的返回值反馈回 B

例如:

举个实例:

#funA 作为装饰器函数
>>>def funA(fn):
    print("人生苦短,我用Python")
    fn() # 执行传入的fn参数
    print("Hello wolrd")
    return "Good Ending"

>>>@funA
def funB():
    print("学习 Python")

##程序运行结果
人生苦短,我用Python
学习 Python
Hello World

>>> funB
Good Ending