Python装饰器

python装饰器

​ 装饰器在 python 里占着举足轻重的地位。第一:体现在写代码的时候。第二:体现在面试的时候,大部分公司都很有可能问有关装饰器的问题。

1. 装饰器的原则和作用

原则:开放封闭原则

  • 开放:对扩展是开放的
  • 封闭:对修改是封闭的

作用:

  • 不修改函数的调用方式,但是还想在原来的函数前后添加功能

2. 装饰器的初成

​ 下面我们完成一个计算函数运行时间的功能:

import time
def func():
    time.sleep(0.01)
    print('我是func函数')

def timmer(f):
    start = time.time()
    f()
    end = time.time()
    print(end - start)

timmer(func)

​ 我们封装了一个计算函数运行时间的功能,这样我们每次想计算函数的运行时间。只需要调 timmer 函数,并且直接把自己需要被计算时间的函数传进 timmer 内就可以了。

​ 但是这样我们还是有一个不好的点,就是每次调用都是去调用的 timmer。如果我们想计算 func 函数的运行时间,我们不去调用 timmer,而是每次去调用 func,是不是就完美了。

​ 于是我们就可以在调用函数的时候在前面赋值给 func:

import time
def func():
    time.sleep(0.01)
    print('我是func函数')

def timmer(f):
    start = time.time()
    f()
    end = time.time()
    print(end - start)

func = timmer  # 这里的timmer没有括号,调用的是内存地址
func()
# func(func) # 不合理

​ 但是如果这样写,我们底部的 “func()”,实际调用的还是 timmer。timmer 调用需要传一个值。如果我们要传入一个值,还需要把 func 函数传进去(详见以上代码注释)。而现在我们上面定义的变量 func 是 timmer,这样子还是不合理的。所以我们可以写成下面这样:

import time  # 第1步
def func():  # 第2步
    time.sleep(0.01)  # 第11步
    print('我是func函数')  # 第12步

def timmer(f):  # 第3步  f --> func的内存地址
    def inner():  # 第5步
        start = time.time()  # 第9步
        f()                  # 第10步(调用func函数)
        end = time.time()  # 第13步
        print(end - start)  # 第14步
    return inner # 第6步(返回inner的内存地址)

func = timmer(func) # 第4步:执行timmer(func)
# 第7步:变量func指向inner内存地址
func()  # 第8步,func()实际执行inner()

​ 以上写法首先要知道,这是一个 “闭包”。大家都知道代码是从上到下运行的。所以程序执行是这个样子的:

  1. 导入 time 模块
  2. 读取 func 函数,并创建一个 func 函数的内存地址
  3. 读取 timmer 函数,并创建一个 timmer 函数的内存地址
  4. 执行 func = timmer(func) 等号后面的 timmer(func),这里调用了 timmer,并把 func 函数传进去
  5. 找到 timmer 函数的内存地址(此时这里的 timmer 函数内的参数 f 指向了函数 func 的内存地址),并读取 timmer 函数内的 inner 函数
  6. timmer 函数将 inner 返回出去(这里的 inner 没有加括号,返回的是 inner 的内存地址)
  7. timmer 函数 return 的 inner 内存地址赋值给了 func 变量,这里的 func 指向 inner 的内存地址
  8. func() 实际上执行的是 inner(),调用 inner 函数
  9. start = time.time()
  10. f(),调用 func 函数,执行 func 函数
  11. time.sleep(0.01)
  12. print(‘我是func函数’)
  13. end = time.time()
  14. print(end - start)

3. 语法糖

​ 在被装饰的函数前面加上 @函数名,就是一个语法糖,等于上面代码块内的 func = timmer(func)

import time

def timmer(f):
    def inner():
        start = time.time()
        f()
        end = time.time()
        print(end - start)
    return inner

@timmer        # 语法糖  @装饰器函数名
def func():  # 被装饰的函数
    time.sleep(0.01)
    print('我是func函数')

# func = timmer(func)
func()

4. 有返回值和参数的语法糖

​ 我们上面还不是完整的装饰器函数,因为我们上面的 func 函数没有返回值和参数。如果有返回值和参数,timmer 装饰器要做出相应的修改

4.1 修改为有返回值的语法糖

import time

def timmer(f):        # 装饰器函数
    def inner():
        start = time.time()
        f()        # 被装饰的函数
        end = time.time()
        print(end - start)
    return inner

@timmer        # 语法糖  @装饰器函数名
def func():  # 被装饰的函数
    time.sleep(0.01)
    print('我是func函数')
    return '我是返回值'

# func = timmer(func)
res = func()    # func() --> inner()
print(res)

​ 以上代码实际上返回的是 None ,因为 res = func() 的 func() 实际上是 inner(),而 inner() 并没有返回值,所以我们应该在 inner 内加入返回值:

import time

def timmer(f):        # 装饰器函数
    def inner():
        start = time.time()
        res = f()        # 被装饰的函数
        end = time.time()
        print(end - start)
        return res
    return inner

@timmer        # 语法糖  @装饰器函数名
def func():  # 被装饰的函数
    time.sleep(0.01)
    print('我是func函数')
    return '我是返回值'

# func = timmer(func)
res = func()  # func() --> inner()
print(res)

​ 因为我们调用 func() 实际调用的是 inner(),所以我们直接在 inner 函数内把传入 timmer 的 func 函数调用并返回出去,就拿到 func 函数内的返回值了

思考

def outer():
    def inner():
        return 'inner'
    inner()

outer()

以上代码能拿到 inner 的返回值吗?

4.2 修改为有参数也有返回值的语法糖

​ 装饰带参数函数的装饰器:

import time

def timmer(f):        # 装饰器函数
    def inner(para):
        start = time.time()
        res = f(para)        # 被装饰的函数
        end = time.time()
        print(end - start)
        return res
    return inner

@timmer        # 语法糖  @装饰器函数名
def func(a):  # 被装饰的函数
    time.sleep(0.01)
    print('我是func函数', a)
    return '我是返回值'

# func = timmer(func)
res = func(1)  # func() --> inner()
print(res)

如果我在以上代码的 func 函数内传入多个参数怎么办?那我们的装饰器内部的 inner 函数的参数也需要改成多个参数:

import time

def timmer(f):        # 装饰器函数
    def inner(para,para2):
        start = time.time()
        res = f(para,para2)        # 被装饰的函数
        end = time.time()
        print(end - start)
        return res
    return inner

@timmer        # 语法糖  @装饰器函数名
def func(a,b):  # 被装饰的函数
    time.sleep(0.01)
    print('我是func函数', a,b)
    return '我是返回值'

# func = timmer(func)
res = func(1,2)  # func() --> inner()
print(res)

如果变成这样子的话,如果我有多个函数,参数数量不一样但都需要用到 timmer 这个装饰器怎么办呢?假如是下面这个样子:

import time

def timmer(f):        # 装饰器函数
    def inner(para,para2):
        start = time.time()
        res = f(para,para1)        # 被装饰的函数
        end = time.time()
        print(end - start)
        return res
    return inner

@timmer        # 语法糖  @装饰器函数名
def func(a,b):  # 被装饰的函数
    time.sleep(0.01)
    print('我是func函数', a,b)
    return '我是返回值'

@timmer        # 语法糖  @装饰器函数名
def func(a):  # 被装饰的函数
    time.sleep(0.01)
    print('我是func函数', a)
    return '我是返回值'

# func = timmer(func)
res = func(1,2)  # func() --> inner()
print(res)

所以我们的装饰器也需要做出相应的改变,利用 动态参数:” *args “,让 inner 去接收,于是装饰器 timmer 就变成了下面这个样子:

def timmer(f):        # 装饰器函数
    def inner(*args): #  *args拿到的是一个元组
        start = time.time()
        res = f(*args)        # 被装饰的函数
        end = time.time()
        print(end - start)
        return res
    return inner

但是上面的写法还是有一个弊端,如果我们按关键字传参怎么办,” *args “ hold 不住啊,所以我们可以变成下面这个样子:

def timmer(f):        # 装饰器函数
    def inner(*args,**kwargs): #  *args拿到的是一个元组
        start = time.time()
        res = f(*args,**kwargs)        # 被装饰的函数
        end = time.time()
        print(end - start)
        return res
    return inner

上面的 “ **kwargs “ 用于接收关键字参数。由此可见,args 和 kwargs 一起,可以 hold 住整个世界!

以上就是完整版的装饰器的形成了

def wrapper(f):        # 装饰器函数  f是被装饰的函数
    def inner(*args,**kwargs):
        '''在被装饰函数之前要做的事'''
        res = f(*args,**kwargs)        # 被装饰的函数
        '''在被装饰函数之后要做的事'''
        return res
    return inner

@wrapper  # 语法糖 @装饰器函数名
def func(a,b):
    '''函数体'''
    return '返回值'

5. 装饰器进阶—带参数的装饰器

​ 问大家一个小问题,如果我想在控制台输出我这个函数的名字怎么办?python其实内部有一个魔术方法提供给了我们:

def func():
    print('我是函数')

print(func.__name__)  # 查看字符串格式的函数名

思考

def wrapper(f):
    def inner(*args,**kwargs):
        res = f(*args,**kwargs)
        return res
    return inner

@wrapper
def func(a):
    print('我是函数func,我的参数是'.format(a))
    return '啦啦啦'

print(func.__name__)

想一想,以上代码块输出的是什么?

输出的这个是不是不太好?现在是 func 函数传入了 wrapper 装饰器函数,全局上等于没有了 func 函数了,func 函数在 wrapper 装饰器内存着呢。但是我们还有一种办法可以正常使用 func 函数内部定义的所有方法(让控制台输出 func ):

from funtools import wraps
def wrapper(f):
    @wraps(f)
    def inner(*args,**kwargs):
        print('装饰前')
        res = f(*args,**kwargs)
        print('装饰后')
        return res
    return inner

@wrapper
def func(a):
    print('我是函数func,我的参数是'.format(a))
    return '啦啦啦'

print(func.__name__)
print(func(1))

这样子就正常啦。

6. 带参数的装饰器

​ 如果我有500个函数都要被同一个装饰器装饰,我就要 @wrapper 五百遍。等什么时候我不需要了,我要删了,我还需要删 @wrapper 五百遍,就很麻烦。如果我利用带参数的装饰器,那么可以一瞬间控制这些

import time

FLAG = True  # 如果有一天我需要删了,直接修改FLAG的值为False

def timmer_out(flags):
    def timmer(f):
        def inner(*args,**kwargs):
            if flags:
                start = time.time()
                res = f(*args,**kwargs)
                end = time.time
                print(end - start)
            else:
                res = f(*args,**kwargs)
            return res
        return inner
    return timmer

@timmer_out(FLAG)
def func():
    time.sleep(0.01)
    print('我是func函数')

@timmer_out(FLAG)
def func2():
    time.sleep(0.01)
    print('我是func2函数')

func()
func2()

7. 多个装饰器装饰一个函数

def wrapper1(f):  # f --> func
    def inner1():
        print('1')
        f()        # func
        print('2')
    return inner1

def wrapper2(f):  # f --> inner1
    def inner2():
        print('3')
        f()     # inner1
        print('4')
    return inner2

@wrapper2  # func = wrapper2(inner1) = inner2
@wrapper1  # func = wrapper1(func) = inner1
def func():
    print('我是func函数')

func()  # --> inner2()

思考

上面的代码块从上到下的执行过程?


  转载请注明: 浩大大 Python装饰器

  目录