首页 > 技术文章 > 装饰器

gengfenglog 2020-12-29 20:14 原文

zs1


装饰器

装饰器介绍

  • 器:指的是工具

  • 装饰:给被装饰对象添加额外的功能

装饰器就是用来为被装饰对象添加额外功能的工具

装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能


装饰器的原则

在添加新功能的时候要遵循开放封闭的原则:软件上线运行之后,应该对扩展功能开放,对修改源代码封闭
  • 原则:

    • 1、不修改被装饰函数内的源代码

    • 2、不修改函数原有的调用方式

装饰器的核心思想就是在遵循原则1和2的基础之上,为被装饰对象添加额外功能


装饰器的实现

函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,装饰器并不是一门新技术,而是由“名称空间+函数对象+闭包函数”组合使用的产物

# 需求:在不修改index函数的源代码和调用方式的前提下为其添加统计运行时间的新功能
import time  # 导入时间模块

def index():  # 一个函数
    time.sleep(1)  # 模拟index运行的秒数
    print('from index')                  

index()  # 调用方式

# 方案一:失败
# 问题:确实添加了新功能,但是修改了源代码
import time

def index():
    start = time.time()  # 统计开始时间
    time.sleep(1)
    print('from index')
    stop = time.time()  # 统计结束时间
    print('运行时间为: %s' %(stop - start))  # 计算差值 

index()

# 方案二:失败
# 问题:看似装饰器的效果实现了,但是引发了重复代码的问题
import time

def index():
    time.sleep(1)
    print('from index')

start = time.time()
index()
stop = time.time()
print('运行时间为: %s' %(stop - start))

# 方案三:失败
# 问题:把index函数名变成变量名func,要为get_time函数的函数体代码传参数,
# 直接用参数传参,装饰对象虽然写活了,但只能装饰index
import time

def index():
    time.sleep(1)
    print('from index')

def get_time(func):
    start = time.time()
    func()  # 被装饰函数
    stop = time.time()
    print('运行时间为: %s' %(stop - start))

wrapper(index)

# 以上三种方案都失败了,那么往下看

装饰器简易版本

# 给函数添加统计执行时间的功能,解决上述问题
import time

def index():  # index = 被装饰对象index函数的内存地址
    time.sleep(3)
    print('from index')
    
def outer(func):  # func = 被装饰对象index函数的内存地址
    def get_time():
        start = time.time()
        func()  # 被装饰对象index函数的内存地址
        stop = time.time()
        print('运行时间为: %s' %(stop - start))
  return get_time  # 将get_time函数名返回出去

# outer-》返回get_time函数的内存地址-》index = get_time函数的内存地址
index = outer(index)  # outer=(被装饰对象index函数的内存地址)
index()
'''
from index
运行时间为: 3.0035877227783203
'''

zs2


装饰器解决参数问题

import time

def index():
    time.sleep(1)
    print('两情若是久长时,又岂在公公母母!!!')

def home(name):  # home = 被装饰对象home函数的内存地址
    time.sleep(2)
    print("欢迎 %s 来到主页" % name)

def outer(func):  # func = 被装饰对象home函数的内存地址
    # func = home
    def get_time(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)  # 被装饰对象home函数的内存地址
        stop = time.time()
        print('运行时间为: %s' % (stop - start))
    return get_time  # 将get_time函数名返回出去

home = outer(home)  # 偷梁换柱:home = get_time函数的内存地址
home('jason')  # get_time('jason',)

index = outer(index)
index()  # get_time()

'''
欢迎 jason 来到主页
运行时间为: 2.0049831867218018
两情若是久长时,又岂在公公母母!!!
运行时间为: 1.0011849403381348
'''

zs6


装饰器解决返回值问题

import time

def index():
    time.sleep(1)
    print('两情若是久长时,又岂在公公母母!!!')
    return 'from index'

def home(name):  # home = 被装饰对象home函数的内存地址
    time.sleep(2)
    print("欢迎 %s 来到主页" % name)
    return 'home index'

def outer(func):  # func = 被装饰对象home函数的内存地址
    # func = home
    def get_time(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print('运行时间为: %s' % (stop - start))
        return res  # 执行完get_time之后返回被装饰函数执行之后的返回值
    return get_time  # 将get_time函数名返回出去


index = outer(index)  # index = get_time函数的内存地址
res = index()  # get_time()
print(res)

home = outer(home)  # home = get_time函数的内存地址
res1 = home('jason')  # get_time('jason',)
print(res1)

'''
两情若是久长时,又岂在公公母母!!!
运行时间为: 1.0042109489440918
from index
欢迎 jason 来到主页
运行时间为: 2.0018317699432373
home index
'''

zs7


认证装饰器

# 访问所有的界面都需要认证,一次认证通过所有界面都可以正常访问
import time
# 全局标志位,记录用户的登录状态
is_login = {'is_login': False}

def index():
    time.sleep(1)
    print('欢迎来到index界面!!!')

def home():
    time.sleep(1)
    print('欢迎来到home界面!!!')

def register():
    time.sleep(1)
    print('欢迎来到登录界面!!!')

# 编写登录认证装饰器
def login_auth(func):
    def auth(*args, **kwargs):
        # 每次调用都会先判断用户是否已经登录
        if is_login.get('is_login'):
            res = func(*args, **kwargs)
            return res
        username = input('请输入用户名>>>:').strip()
        password = input('请输入密码>>>:').strip()
        if username == 'jason' and password == '123':
            res = func(*args, **kwargs)  # 正常执行函数index
            is_login['is_login'] = True  # 将记录用户登录状态的数据修改
            return res
        else:
            print('用户名或密码输入错误!!!')
    return auth

"""
在调用index之前需要用户输入用户名和密码认证
正确才可以调用,错误直接拒绝,执行下一个需要认证的函数
"""
index = login_auth(index)
index()

home = login_auth(home)
home()

register = login_auth(register)
register()

"""
请输入用户名>>>:jason
请输入密码>>>:123
欢迎来到index界面!!!
欢迎来到home界面!!!
欢迎来到登录界面!!!
"""

无参装饰器固定模板

def outer(func):     
    def inner(*args,**kwargs):  # inner,在inner函数内写被装饰的函数
      	print('执行被装饰函数之前可以添加的额外功能')
        res = func(*args,**kwargs)  # 执行被装饰的函数
        print('执行被装饰函数之后可以添加的额外功能')
        return res  # # 将被装饰函数执行之后的返回值返回
    return inner 
  
"""
func(*args,**kwargs) # *args,**keargs让参数保持一致,装的像一点
return res  # 让返回值保持一致,装的像一点
"""
# 以上的装饰器为被装饰对象啥新功能也没添加,就是无参装饰器模板

zs3


装饰器语法糖

语法糖:让你开心的语法
  • 装饰器语法糖书写规范
    • 语法糖必须紧贴在被装饰对象的上方
  • 装饰器语法糖内部原理
    • 会自动将紧贴着的被装饰对象名字当做参数加括号传给装饰器函数自动调用
    • 如果上方没有其他语法糖则直接使用与被装饰对象相同的变量名接收装饰器的返回值
def outer(func):  # func = 被装饰对象index函数的内存地址
    def inner(*args, **kwargs):
        print('执行函数之前可以添加的额外功能')
        res = func(*args, **kwargs)  # 执行被装饰的函数
        print('执行函数之后可以添加的额外功能')
        return res  # 将被装饰函数执行之后的返回值返回
    return inner
  
# 在被装饰对象正上方的单独一行写@装饰器名字  
@outer  # index = outer(index)
def index(*args, **kwargs):
    print('from index')
    
@outer  # home = outer(home)
def home():
    print('from home')
    
"""
@outer  index = outter(index)  
outer(被装饰对象index函数的内存地址) -> 返回inner函数
的内存地址-> index = inner函数的内存地址                                 
"""

双层语法糖

# 统计函数运行时间装饰器
import time

def time_filter(func):
    def get_time(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('函数的运行时间为:%s' % (stop_time - start_time))
        return res
    return get_time


# 校验用户登录装饰装饰器
is_login = {'is_login': False}  # 全局标志位

def login_auth(func):
    def auth(*args, **kwargs):
     		# 判断用户是否登录
        if is_login.get('is_login'):  
            res = func(*args, **kwargs)
            return res
        # 先获取用户的用户名和密码
        username = input('请输入用户名>>>:').strip()
        password = input('请输入密码>>>:').strip()
        # 校验用户名和密码是否正确
        if username == 'jason' and password == '123':
            print('登录成功!!!')
            res = func(*args, **kwargs)
            is_login['is_login'] = True
            return res
        else:
            print('用户名或密码错误,无权限执行!!!')
    return auth


@login_auth
@time_filter
def index(res):
    time.sleep(2)
    print('我是index'.center(30))
    return '函数1>>>: %s' % res

print(index('ok'))

@login_auth
@time_filter
def home(res):
    time.sleep(2)
    print('我是home'.center(30))
    return '函数2>>>: %s' % res

print(home('no'))

'''
请输入用户名>>>:jason
请输入密码>>>:123
登录成功!!!
           我是index            
函数的运行时间为:2.004554033279419
函数1>>>: ok
            我是home            
函数的运行时间为:2.003156900405884
函数2>>>: no

'''

zs4


叠加多个装饰器

def decorate1(func1):  # func1 = inner2的内存地址
    print('加载了 decorate1')
    def inner1(*args, **kwargs):
        print('执行了 inner1')
        res1 = func1(*args, **kwargs)
        return res1
    return inner1

def decorate2(func2):  # func2 = inner3的内存地址
    print('加载了 decorate2')
    def inner2(*args, **kwargs):
        print('执行了 inner2')
        res2 = func2(*args, **kwargs)
        return res2
    return inner2

def decorate3(func3):  # func3 = 被装饰index的内存地址
    print('加载了 decorate3')
    def inner3(*args, **kwargs):
        print('执行了 inner3')
        res3 = func3(*args, **kwargs)
        return res3
    return inner3

#                           index = inner1的内存地址
@decorate1  # decorate1(inner2的内存地址) 返回 inner1的内存地址
@decorate2  # decorate2(inner3的内存地址) 返回 inner2的内存地址
@decorate3  # decorate3(被装饰index的内存地址) 返回 inner3的内存地址
def index():
    print('我是 index')

index()     
# 语法糖由下往上自动执行

image-20211117215616152

装饰器修复技术

'''如何做到无法轻易看出是否被装饰'''
from functools import wraps  # 固定句式
def outer(func):
    @wraps(func)  # 修复技术就是为了让被装饰对象更加不容易被察觉装饰了
    def inner(*args, **kwargs):
        print('执行被装饰函数之前可以添加的额外功能')
        res = func(*args, **kwargs)  # 执行被装饰的函数
        print('执行被装饰函数之后可以添加的额外功能')
        return res  # 将被装饰函数执行之后的返回值返回
    return inner


@outer  # index = outer(index)
def index():
    print('from index')
print(index)
help(index)

def home():
    """这是一个home函数"""
    print('from home')
help(index)  # help() 可以查看函数的基本信息(函数名 函数注释)
# help(home)
# print(index)
# help(len)

zs5

有参装饰器

# 如果装饰器内部还需要额外的参数,就需要用到有参装饰器
def outer(source_data):
    # source_data = 'file'
    def login_auth(func):
        def auth(*args,**kwargs):
            # 2.校验用户名和密码是否正确
            # 数据的校验方式可以切换多种
            if source_data == 'file':
                # 从文件中获取用户数据并比对
                print('file文件获取')
            elif source_data == 'MySQL':
                # 从MySQL数据库中获取数据比对
                print('MySQL数据库获取')
            elif source_data == 'postgreSQL':
                # 从postgreSQL数据库中获取数据对比
                print('postgreSQL数据库获取')
            else:
                print('用户名或密码错误 无法执行函数')
        return auth
    return login_auth

@outer('file')
def index():
    print('from index')
@outer('MySQL')
def home():
    print('from home')

index()
home()

推荐阅读