python装饰器

装饰器

简介

装饰器并不是一个新的知识点,而是由前面所有的函数知识点整合到一起的产物。

装饰器的本质:在不改变被装饰对象原有的“调用方式”和“内部代码”的情况下给被装饰对象添加新的功能。

这是不是听起来有些神奇?装饰器的原则就是对扩展开放,对修改封闭。

image

简单版本装饰器

了解完什么是装饰器之后,我们先学习使用简单版本的装饰器。

现在有一段代码:

import time  # 导入模块,不用管什么意思
def index():
    time.sleep(1)  # 让程序休眠1秒钟
    print('这里是index函数')

def home():
    time.sleep(1)  # 让程序休眠1秒钟
    print('这里是home函数')

我如何能在不动上面2个函数的情况下,添加一个功能:输出函数运行的时长。

这时候有人就要说了:啊?这不是很简单,我只要添加一个函数

def get_time(function_name):
    start_time = time.time()  # 获取运行函数前的时间
    function_name()  # 调用函数
    end_time = time.time()  # 获取运行结束后的时间
    print(end_time - start_time)  # 输出时间差

get_time(index)
get_time(home)

image

这个方法确实是可以,但是运行函数都要调用get_time()。

所以别着急,装饰器的功能还没了解全之前,请先继续看下去。

实现:

import time  # 导入模块,不用管什么意思

# 装饰对象
def index():
    time.sleep(1)  # 让程序休眠1秒钟
    print('这里是index函数')

def home():
    time.sleep(1)  # 让程序休眠1秒钟
    print('这里是home函数')
    
# 装饰器
def outer(function_name):
    def inner():
        # 获取运行函数前的时间
        start_time = time.time()  
        # 调用名为function_name函数
        function_name()  
        # 获取函数运行结束后的时间
        end_time = time.time()  
        # 输出时间差
        print(end_time - start_time)  
	# 返回内部函数名
    return inner
# 让index()变成inner()
index = outer(index)
index()  
"""
输出:
这里是index函数
1.0101792812347412
"""

# 狸猫换太子
home = outer(home)
home()  
"""
输出:
这里是home函数
1.0047881603240967
"""

这个时候函数只需要调用它自己就能输出运行时间了,相当于给函数内部添加了一段代码一样。

进阶版本装饰器

这时候如果函数有参数该怎么做呢?并且参数还不知道几个?

很简单,只要加上可变长形参就行了。

实现:

import time

def index(a):  # 有参数
    time.sleep(1) 
    print('这里是index函数', a)

def home():  # 无参数
    time.sleep(1)  
    print('这里是home函数')
    
def outer(function_name):
    def inner(*args, **kwargs):  # 添加可变长形参
        start_time = time.time()  
        function_name(*args, **kwargs)  # 传参
        end_time = time.time()  
        print(end_time - start_time)  
    return inner
index = outer(index)
index(555)  
"""
输出:
这里是index函数 555
1.0047197341918945
"""

home = outer(home)
home()  
"""
输出:
这里是home函数
1.0003490447998047
"""

完整版本装饰器

这个时候问题又出现了,如果函数有返回值又该怎么办?

也很简单,在装饰器内部函数加一个return返回值就行了。

实现:

import time

def index(a):
    time.sleep(1) 
    print('这里是index函数')
    return a  # 有返回值

def home():
    time.sleep(1)  
    print('这里是home函数')
    
def outer(function_name):
    def inner(*args, **kwargs): 
        start_time = time.time()  
        # 将函数返回值赋值给res
        res = function_name(*args, **kwargs)  
        end_time = time.time()  
        print(end_time - start_time)  
        """返回函数的返回值"""
        return res
    return inner
index = outer(index)
print(index(555))
"""
输出:
这里是index函数
1.0073633193969727
555
"""

home = outer(home)
home()  
"""
输出:
这里是home函数
1.0051474571228027
"""

装饰器模板

还没搞明白装饰器怎么回事?小问题!这里有一套万能模板,只要知道怎么使用装饰器就行了。

image

'''编写装饰器其实有一套固定的代码 不需要做任何理解'''
def outer(func_name):  # func_name用于接收被装饰的对象(函数)
    def inner(*args, **kwargs):
        print('执行被装饰函数之前 可以做的额外操作')
        res = func_name(*args, **kwargs)  # 执行真正的被装饰函数
        print('执行被装饰函数之后 可以做的额外操作')
        return res  # 返回真正函数的返回值
    return inner

装饰器语法糖

由于感觉在这块代码太繁琐了

index = outer(index)
index()  

于是python给了一个方法,用@+装饰器名称放在装饰对象的上方。

实现:

def outer(func_name):  
    def inner(*args, **kwargs):
        # 额外操作
        res = func_name(*args, **kwargs) 
        # 额外操作
        return res
    return inner

# 装饰器语法糖
@outer
def index():
    print('这里是index函数')

"""可以直接使用index(),省去了一个步骤"""
index()

语法糖内部原理:

  1. 使用的时候最好紧跟在被装饰对象的上方
  2. 语法糖会自动将下面紧挨着的函数名传给@后面的函数调用

image

装饰器修复技术

在了解装饰器修复技术之前,我们先了解被装饰对象的内部属于谁了

def outer(func_name):  
    def inner(*args, **kwargs):
        res = func_name(*args, **kwargs) 
        return res
    return inner

@outer
def index():
    print('这里是index函数')

print(index)
"""
输出:
<function outer.<locals>.inner at 0x0000025BA5B9F268>
"""

可以看出,这时的index已经是inner的人了,这就让人很不爽了,所以我们要把它恢复回来。

from functools import wraps  # 导入模块
def outer(func_name):  
    @wraps(func_name)  # 修复
    def inner(*args, **kwargs):
        res = func_name(*args, **kwargs) 
        return res
    return inner

@outer
def index():
    print('这里是index函数')

print(index)
"""
输出:
<function index at 0x00000133924FF268>
"""

可以看到,index还是原来的index。

多层装饰器

我们已经知道了语法糖的作用是将装饰对象自动装饰到装饰器中,一个语法糖的应用我们已经学会了,那么多个语法糖该怎么应用呢?

让我们来看一串代码:

# 装饰器outer1
def outer1(func1):
    print('加载了outer1')
    def wrapper1(*args, **kwargs):
        print('执行了wrapper1')
        res1 = func1(*args, **kwargs)
        return res1
    return wrapper1

# 装饰器outer2
def outer2(func2):
    print('加载了outer2')
    def wrapper2(*args, **kwargs):
        print('执行了wrapper2')
        res2 = func2(*args, **kwargs)
        return res2
    return wrapper2

# 装饰器outer3
def outer3(func3):
    print('加载了outer3')
    def wrapper3(*args, **kwargs):
        print('执行了wrapper3')
        res3 = func3(*args, **kwargs)
        return res3
    return wrapper3

# 连用三个语法糖
@outer1
@outer2
@outer3
def index():
    print('from index')

当连用多个语法糖的时候,优先执行靠近装饰对象的语法糖,并且直到最远的语法糖才会赋值给装饰对象的函数名。

也就是说,在上述代码中,优先执行@outer3,将wrapper3装饰到index中,但此时不会赋值给index函数名,执行到@outer1时,这时就会执行赋值语句,也就是index = outer3(index)。

这三句语法糖总的来说:index = outer3(outer2(outer1(index))),就相当于执行力这段语句。

执行结果:
image

有参装饰器

我们都知道,装饰器里面只有一个形参,这个形参用于接收函数的名字,那么我们应该如何在给装饰器传一些额外参数进去呢?

需要一些额外参数的装饰器:

def outer(func_name):
    def inner(*args, **kwargs):
        """此时这里需要一个参数用于决定处理数据的方式"""
        if source_data == '1':
            print('操作方式1')
        elif source_data == '2':
            print('操作方式1')
        elif source_data == '3':
            print('操作方式1')
        else:
            print('其他操作情况')
        res = func_name(*args, **kwargs)
        return res

    return inner


@outer()
def index():
    print('from index')

这个装饰器里的source_data应该在哪传进去呢?

很明显,装饰器outer括号里面是不能更改的,因为它要用于接收函数名,inner显然也是不能更改的,它要用于接收装饰对象的参数。

image

解决方法:在装饰器外层在套一个函数

# 最外层函数,可以接收参数给装饰器使用
def outer_outer(source_data):
    # 装饰器
    def outer(func_name):
        def inner(*args, **kwargs):
            if source_data == '1':
                print('操作方式1')
            elif source_data == '2':
                print('操作方式1')
            elif source_data == '3':
                print('操作方式1')
            else:
                print('其他操作情况')
            res = func_name(*args, **kwargs)
            return res

        return inner

    # 这里返回装饰器的函数名
    return outer

"""
这段语法糖会优先执行函数
因为outer_outer函数返回的是装饰器的名字
所以变成了@outer,又回到了最初的语法糖
"""
@outer_outer('3')
def index():
    print('from index')
posted @ 2022-03-18 16:46  Yume_Minami  阅读(1817)  评论(1编辑  收藏  举报