zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Python基础09-装饰器

Python基础 09 装饰
2023-06-13 09:11:05 时间

-曾老湿, 江湖人称曾老大。


-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。


装饰器介绍


什么是装饰器

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

装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。
强调装饰器的原则:1 不修改被装饰对象的源代码 2 不修改被装饰对象的调用方式
装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能

为什么要用装饰器

开放封闭原则:对修改封闭,对扩展开放

装饰器的实现,必须遵循两大原则。 1.不修改被装饰对象的源代码 2.不修改被装饰对象的调用方式

装饰器的目标,就是在遵循1和2原则的前提下为被装饰对象添加上新功能


装饰器简单版

# 模拟网站响应
import time

def index():
    print('welcome to index page')
    time.sleep(3)

# 需求,给上面的index函数,加一个新功能,统计他的执行时间
import time

def index():
    print('welcome to index page')
    time.sleep(3)


start=time.time()
index()
stop=time.time()
print('Run Time Is: %s' %(stop - start))

# 是不是满足了两大原则?
# 1.没有改变被装饰对象的源代码
# 2.没有改变被装饰对象的调用方式

# 哦~~~~这原来就是装饰器啊...
# 装饰你妹啊~

# 想一个问题,如果我们有login函数,register函数...等1万个函数,改怎么写?

start=time.time()
index()
stop=time.time()
print('Run Time Is: %s' %(stop - start))

start=time.time()
register()
stop=time.time()
print('Run Time Is: %s' %(stop - start))

start=time.time()
login()
stop=time.time()
print('Run Time Is: %s' %(stop - start))

# 智障...全都是重复代码,可以去屎了,那么我们来优化一下代码
import time

def index():
    print('welcome to index page')
    time.sleep(3)


def wrapper(func):
    start=time.time()
    func()
    stop=time.time()
    print('Run Time Is: %s' %(stop - start))

wrapper(index)

# 完美了吗?并没有,因为每次调用我们还需要传参...
import time

def index():
    print('welcome to index page')
    time.sleep(3)

def outter(func):
    def wrapper():
        start=time.time()
        func()
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
    return  wrapper

f=outter(index)
f()

# 变成"装饰器"写法,但是好像不满足两大原则,我们修改了函数的调用方式,下面的代码 ,搞定
import time

def index():
    print('welcome to index page')
    time.sleep(3)

def outter(func):
    def wrapper():
        start=time.time()
        func()
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
    return  wrapper

index=outter(index)
index()

装饰器升级版

import time
def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)

# 上面这个函数,是一个需要传参的函数,调用方法如下
home('zls')

# 需求,使用装饰器,调用
import time

def index():
    print('welcome to index page')
    time.sleep(3)

def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)

def outter(func):
    def wrapper(name):
        start=time.time()
        func(name)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
    return  wrapper

home=outter(home)
home('zls')

# 完美?那么我们把index在调用一下试试?
import time

def index():
    print('welcome to index page')
    time.sleep(3)

def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)

def outter(func):
    def wrapper(name):
        start=time.time()
        func(name)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
    return  wrapper

index=outter(index)
index()

# home=outter(home)
# # home('zls')

emm... 报错了,为啥呢?因为把为了写home函数的装饰器,把wrapper改了啊,需要一个参数

import time

def index():
    print('welcome to index page')
    time.sleep(3)

def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)

def outter(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
    return  wrapper

index=outter(index)
index()

home=outter(home)
home('zls')

那么问题来了,如果函数有返回值怎么办?

import time

def index():
    print('welcome to index page')
    time.sleep(3)

def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)
    return 123

def outter(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
    return  wrapper

home=outter(home)
res=home('zls')
print(res)

改进一下纸

import time

def index():
    print('welcome to index page')
    time.sleep(3)

def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)
    return 123

def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
        return res
    return  wrapper

index=timmer(index)
index()

home=timmer(home)

res=home('zls')
print(res)

装饰器语法糖

import time

def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
        return res
    return  wrapper

@timmer
def index():
    print('welcome to index page')
    time.sleep(3)

# 语法糖,把装饰器,放到被装饰对象上面加上@
@timmer
def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)
    return 123

index()

res=home('zls')
print(res)

装饰器模板

def outter(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return wrapper

@outter

小练习:认证功能的装饰器

import time

def auth(func):
    def wrapper(*args,**kwargs):
        inp_user=input('please input your username: ').strip()
        inp_pwd=input('please input your password: ').strip()

        if inp_user == 'zls' and inp_pwd == '123':
            print('login successfull')
            res=func(*args,**kwargs)
        return res
    return wrapper

@auth
def index():
    print('welcome to index page')
    time.sleep(3)

index()

叠加多个装饰器

import time

def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
        return res
    return  wrapper

def auth(func):
    def wrapper(*args,**kwargs):
        inp_user=input('please input your username: ').strip()
        inp_pwd=input('please input your password: ').strip()

        if inp_user == 'zls' and inp_pwd == '123':
            print('login successfull')
            res=func(*args,**kwargs)
        return res
    return wrapper

@auth
@timmer
def index():
    print('welcome to index page')
    time.sleep(3)

index()

注意:当我们有多个装饰器的时候,解释语法是自下而上运行,但是如果是装饰器的话,自上而下运行,如果我们把timmer装饰器放在auth上面,那么运算的时间,会把auth函数的时间加进去

有参装饰器

之前我们用的都是无参装饰器,现在我们要让装饰器可以传递参数

我们来修改一下认证的源,之前我们认证都是文件,企业中会把用户存储在MySQL中或者LDAP

import time

def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
        return res
    return  wrapper

def auth(func):
    def wrapper(*args,**kwargs):
        if engine == 'file':
            inp_user=input('please input your username: ').strip()
            inp_pwd=input('please input your password: ').strip()
            if inp_user == 'zls' and inp_pwd == '123':
                print('login successfull')
                res=func(*args,**kwargs)
                return res
            else:
                print('username or password error')
        elif engine == 'mysql':
            print('基于MySQL的认证机制')
        elif engine == 'ldap':
            print('基于LDAP的认证机制')
        else:
            print('无法识别的认证源')
    return wrapper

@auth
@timmer
def index():
    print('welcome to index page')
    time.sleep(3)
index()

代码写完了,但是我需要知道engine是什么,然后找对应的认证方式,需要传参使用闭包

import time

def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
        return res
    return  wrapper

def engine_auth(engine='file'):
    def auth(func):
        def wrapper(*args,**kwargs):
            if engine == 'file':
                inp_user=input('please input your username: ').strip()
                inp_pwd=input('please input your password: ').strip()
                if inp_user == 'zls' and inp_pwd == '123':
                    print('login successfull')
                    res=func(*args,**kwargs)
                    return res
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print('基于MySQL的认证机制')
            elif engine == 'ldap':
                print('基于LDAP的认证机制')
            else:
                print('无法识别的认证源')
        return wrapper
    return auth

@engine_auth('mysql')
@timmer
def index():
    print('welcome to index page')
    time.sleep(3)
index()

装饰器,最多就三层,最外面层可以随便传递参数


无参装饰器模板

def outter(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return wrapper

有参装饰器模板

def outter(x,y,z):
    def outter2(func):
        def wrapper(*args,**kwargs):
            res=func(*args,**kwargs)
            return res
        return wrapper
    return outter2

解决程序bug

import time

def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
        return res
    return  wrapper

def engine_auth(engine='file'):
    def auth(func):
        def wrapper(*args,**kwargs):
            if engine == 'file':
                inp_user=input('please input your username: ').strip()
                inp_pwd=input('please input your password: ').strip()
                if inp_user == 'zls' and inp_pwd == '123':
                    print('login successfull')
                    res=func(*args,**kwargs)
                    return res
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print('基于MySQL的认证机制')
            elif engine == 'ldap':
                print('基于LDAP的认证机制')
            else:
                print('无法识别的认证源')
        return wrapper
    return auth

@engine_auth('file')
@timmer
def index():
    print('welcome to index page')
    time.sleep(3)
index()


@engine_auth('file')
@timmer
def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)
    return 123
res=home('zls')
print(res)

我们在两个函数前面都加上认证模块

然后这个程序就智障了,mmp,让我登录两次?你们见过哪个网站登录过一次,还需要登录第二次?

比如你再京东买东西...一开始登录了,然后当你点购物车还要登录?点结算还要登录?疯了吧

所以我们需要记录一下用户的登录状态...

import time

current_user={'username':None}

def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('Run Time Is: %s' %(stop - start))
        return res
    return  wrapper

def engine_auth(engine='file'):
    def auth(func):
        def wrapper(*args,**kwargs):
            if current_user['username']:
                print('已经登录过了,无需再次登录')
                res=func(*args,**kwargs)
                return res
            if engine == 'file':
                inp_user=input('please input your username: ').strip()
                inp_pwd=input('please input your password: ').strip()
                if inp_user == 'zls' and inp_pwd == '123':
                    print('login successfull')
                    current_user['username']=inp_user  #记录登录状态
                    res=func(*args,**kwargs)
                    return res
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print('基于MySQL的认证机制')
            elif engine == 'ldap':
                print('基于LDAP的认证机制')
            else:
                print('无法识别的认证源')
        return wrapper
    return auth

@engine_auth('file')
@timmer
def index():
    print('welcome to index page')
    time.sleep(3)
index()


@engine_auth('file')
@timmer
def home(name):
    print('welcome %s to home page' %name)
    time.sleep(2)
    return 123
res=home('zls')
print(res)

装饰器伪装注释信息


了解函数注释的作用

import time

def demo(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return  wrapper

def index():
    """
    index功能
    """
    print('进入index页面')
    time.sleep(3)

# 查看函数名
print(index.__name__)

# 查看函数注释信息
print(index.__doc__)

# 查看函数的注释信息
print(help(index))

现在我们把装饰器加上

import time

def demo(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return  wrapper

@demo
def index():
    """
    index功能
    """
    print('进入index页面')
    time.sleep(3)

print('--- 函数名:---')
print(index.__name__)

print('--- 注释信息1 ---')
print(index.__doc__)

print('--- 注释信息2 ---')
print(help(index))

当我们给一个函数加上装饰器后,会发现,函数名变了,注释信息没了,这样的话,别人无法查看你的这个功能,用不明白,那就是垃圾代码。

那肿么办?我们把注释信息加在wrapper里面?

import time

def demo(func):
    def wrapper(*args,**kwargs):
        """
        index功能
        """
        res=func(*args,**kwargs)
        return res
    return  wrapper

@demo
def index():
    """
    index功能
    """
    print('进入index页面')
    time.sleep(3)

print('--- 函数名:---')
print(index.__name__)

print('--- 注释信息1 ---')
print(index.__doc__)

print('--- 注释信息2 ---')
print(help(index))

这样写,那就是智障...写死了,如果我把装饰器加在别的函数上呢?还是index功能嘛?

import time

def demo(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    # 将wrapper函数的函数名赋值给func函数,也就是传递进来的函数(被装饰对象)
    wrapper.__name__ = func.__name__
    # 将wrapper函数的注释信息赋值给func函数,也就是传递进来的函数(被装饰对象)
    wrapper.__doc__ = func.__doc__
    return  wrapper

@demo
def index():
    """
    index功能
    """
    print('进入index页面')
    time.sleep(3)

print('--- 函数名:---')
print(index.__name__)

print('--- 注释信息1 ---')
print(index.__doc__)

print('--- 注释信息2 ---')
print(help(index))

但是...咱们可以看看一个函数下面有多少个__xxx__的方法,上面才有两个

我们需要使用python内置的一个装饰器

from functools import wraps
import time

def demo(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return  wrapper

@demo
def index():
    """
    index功能
    """
    print('进入index页面')
    time.sleep(3)

print('--- 函数名:---')
print(index.__name__)

print('--- 注释信息1 ---')
print(index.__doc__)

print('--- 注释信息2 ---')
print(help(index))