【python】高级特性之装饰器

【摘要】上篇博文介绍了闭包,闭包还有一个常用场景就是装饰器。因此,本篇博文将介绍装饰器

1.装饰器

试想一下这个场景,如果我们现在有一份代码,可以给用户提供打游戏、看视频,但是产品经理突然告知我们要对用户进行年龄判断,如果是未成年人,则这两个程序不对用户执行;如果是成年人,则执行相应程序。那么,难道我们要改代码吗?
又或者,我们需要在每一次的节日都打印该节日快乐,难道要频繁更改代码内容吗?
在Python中,针对这一情况,我们可以使用装饰器来予以解决。

1.1 装饰器的概念

装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。

1.2 为何使用装饰器

有一些场景,需要频繁更改逻辑,但是遵循 开放封闭 原则,虽然在这个原则是用于面向对象开发,但是也适用于函数式编程。简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展。即:
封闭:已实现的功能代码块
开放:对扩展开发

装饰器可以帮助我们:
1).快速的在不改变原有函数的基础上,添加逻辑信息
2).不改变函数的调用方式

来看一个简单的例子(无参数的函数),假如下面这两个就是已经实现好的播放视频以及游戏代码块

def videos():
    print("正在播放视频.....")

def game():
    print("正在进入游戏.....")

现在我们要对未成年人进行限制,并且不能在封装好的代码块中修改。所以我们只能使用装饰器,而装饰器其实就是闭包的一个应用场景,那肯定要满足闭包的三个条件;

age = 13
#1). 外部函数里面也定义了一个函数
def is_adult(fun):
    def wrapper():
        if age >= 18:
        	2).内部函数调用了外部函数的临时变量
            fun()
        else:
            print("未成年")

    # 3).返回的是函数的引用
    return wrapper

上面的代码就是我们所说的装饰器。那么如何使用装饰器呢?

#用装饰器is_adult装饰函数videos,并将返回只wrapper赋值给videos函数
videos=is_adult(videos)
#调用videos函数——>实质上就是调用wrapper函数内部代码
videos()

由于上面age变量的值为13,因此在调用wrapper函数后,执行了对age的判断,<18打印了未成年
在这里插入图片描述
如果把age变量赋值为35

age = 35

再调用上面的代码

videos=is_adult(videos)
videos()

即:
在这里插入图片描述
则执行效果如下:
在这里插入图片描述
但是这样装饰函数,实属麻烦。python中的语法糖可以给我们提供同样的作用,帮我们装饰需要装饰的函数,其语法如下:(只需要在代码块上面添加:@装饰器名称)

@is_adult   #######语法糖所执行的效果就是:videos = is_adult(videos)
def videos():
    print("正在播放视频.....")

@is_adult  #######语法糖所执行的效果就是:game = is_adult(game)
def game():
    print("正在进入游戏.....")

再来一个听音乐的代码块,没有用装饰器装饰

def music():
    print("正在播放音乐.....")

我们现在给变量age赋值13,然后依次调用这三个函数,执行效果如下:
在这里插入图片描述
如果变量age=19,执行效果如下:
在这里插入图片描述
这是最简单的装饰器例子,无参数的函数装饰器。在下面的例子中,我们还需要掌握被装饰的函数有参数、被装饰的函数有可变参数和关键字参数、被多个装饰器装饰的函数,最后总结出通用装饰器的模板。

2.装饰器的应用场景

2.1 插入日志

日志要显示的内容有:当前时间、主机名、当前正在运行的程序名称、运行结果
我们的思路是:利用time模块获取当前时间,利用os模块获取主机名,再用sys模块获取程序名称,最后对这些字段进行拼接。装饰器内容如下:

def add_log(fun):
    def wrapper(*args, **kwargs):  
        # 获取被装饰的函数的返回值
        result = fun(*args, **kwargs)     
        # 返回当前的字符串格式时间
        now_time = time.ctime()
        # 获取主机名 nodename='foundation0.ilt.example.com'
        hostname = os.uname().nodename.split('.')[0]
        # 获取运行的程序
        process_full_name = sys.argv[0]
        process_name = os.path.split(process_full_name)[-1]
        # 日志内容
        # 获取函数名: 函数名.__name__
        info ="函数[%s]的运行结果为%s" %(fun.__name__, result)
        log = " ".join([now_time, hostname, process_name, info])
        print(log)
        return  result
    return  wrapper

def wrapper(*args, **kwargs)这一行代码中的 *args, **kwargs表示接收函数调用时的任意传参。其中 *args接收到的是一个元组, **kwargs接收到的是一个字典。
fun(*args, **kwargs)是将接收到的参数解包。

获取主机名的代码为什么那样写,可以看下图,就会比较明晰:
在这里插入图片描述
以及获取当前正在运行的程序名代码实现:
在这里插入图片描述
装饰器写好后,我们来装饰其他函数
在这里插入图片描述
在register函数上面使用语法糖,调用add_log这个装饰器来装饰register函数。所作的流程如注释。即在第42行调用register函数时,其实是将该函数中的参数传给装饰器中的wrapper函数,并执行。
定义wrapper函数时,参数 *args, **kwargs接收所有任意参数,接收成功后,调用fun(*args, **kwargs)执行register函数体,函数体是打印参数(name、age、province、gender)的值,并将返回值赋给result。
再执行warpper函数体下面的内容。

3.通用装饰器的模板

掌握下面这个模板,装饰器就稳了~只需要在里面添加内容即可。

def decorate(fun):
    def wrapper(*args, **kwargs):    # args, kwargs是形参
    	# 在函数之前做的操作
        result = fun(*args, **kwargs)  # *args, **kwargs是实参, *args, **kwargs是在解包
        # result接收被装饰函数的返回值;
        # 在函数之后添加操作
        return result
    return wrapper
   
#@装饰器的名字 
@decorate    ===> add=decorate(add)  ---> add指向wrapper函数位置
def add():
    return 'ok'

#调用被装饰的函数
add()

4.多个装饰器装饰

上面我们看了一个装饰器装饰一个函数的例子,如果是多个装饰器装饰一个函数,执行流程如何呢?
例子一
这里有两个装饰器,分别为:1). 判断用户是否登录?2). 判断用户是否有权限?

def is_login(fun):
    def wrapper1(*args, **kwargs):
        print("判断是否登录......")
        result = fun(*args, **kwargs)
        return  result
    return  wrapper1

def is_permission(fun):
    def wrapper2(*args, **kwargs):
        print("判断是否有权限......")
        result = fun(*args, **kwargs)
        return  result
    return  wrapper2

用这两个装饰器装饰函数delete()

@is_login   
@is_permission
def delete():
   print('正在删除学生信息')
    return  '删除学生信息完毕'

执行被装饰的delete(),用result接收delete()的返回值,并打印。

result = delete()
print(result)

执行效果如下:
在这里插入图片描述

用这个简单的例子,来分析一下执行流程:
1). delete = is_perssion(delete) # delete实际上是wrapper2
先用下面这个装饰器is_permission装饰delete函数,装饰完之后,delete函数指向wrapper2。
2). delete = is_login(delete) # delete = is_login(wrapper2) # delete实际上是wrapper1
再用上面的装饰器is_login装饰delete函数(此时的delete函数其实是wrapper2),装饰完wrapper2函数后返回给delete,delete函数此时指向wrapper1

上面这两步是装饰过程,装饰完成之后,再调用detele函数
3). delete() —> wrapper1() —> wrapper2() —> delete()
此时调用delete函数其实就相当于调用wrapper1,进入wrapper1函数体内执行print(“判断是否登录…”),再执行 result = fun(*args, **kwargs),但其实这个fun指的是wrapper2函数,因此这句调用执行wrapper2函数体。
先执行print(“判断是否有权限…”),再执行 result = fun(*args, **kwargs)。这个fun才是真正的delete函数,执行delete函数体内容,并将delete函数的返回值获取到赋值给wrapper2中的 result,wrapper2函数体执行完毕后将返回值result带回给wrapper1函数体中的result,执行完wrapper1中的函数体将返回值result带回给调用语句result = delete()的result,最后打印这个返回值。

嗯,是有点绕,如果分析不出来其执行流程的话,记住以下两句话:
1.装饰过程: 函数先被下面的装饰器装饰,后被上面的装饰器装饰
2.执行过程:上面装饰器先执行, 下面的装饰器后执行

例子二
假设我们要执行删除学生信息的程序,但是要求1)用户登陆成功 2)用户拥有权限
下面看一下代码:
首先系统用户的信息应该存储在数据库中,用户登陆时获取到用户登陆信息,与数据库中的信息作匹配,这里我们用字典存储系统中所有用户的信息。只有两个用户root和admin。

db = {
    'root': {
        'name': 'root',
        'passwd': '123',
        'is_super': 1  # 0-不是 1-是
    },
    'admin': {
        'name': 'admin',
        'passwd': '456',
        'is_super': 0  # 0-不是 1-是
    }
}

再用一个空字典,来存储当前登陆用户的信息

login_user_session = {}

下面是装饰器is_login,用来判断用户是否登录, 如果没有登录,先登录

def is_login(fun):
    def wrapper1(*args, **kwargs):
        if login_user_session:
            result = fun(*args, **kwargs)
            return result
        else:
            print("请您登陆".center(50, '*'))
            user = input("User: ")
            passwd = input('Password: ')
            if user in db:
                if db[user]['passwd'] == passwd:
                    login_user_session['username'] = user
                    print('登录成功')
                    # ***** 用户登录成功, 执行删除学生的操作;
                    result = fun(*args, **kwargs)
                    return result
                else:
                    print("密码错误")
            else:
                print("用户不存在")

    return wrapper1

第二个装饰器是is_permission,用来判断用户是否有权限进行删除操作。

def is_permission(fun):
    def wrapper2(*args, **kwargs):
        print("判断是否有权限......")
        current_user = login_user_session.get('username')
        permissson = db[current_user]['is_super']
        if permissson == 1:
            result = fun(*args, **kwargs)
            return result
        else:
            print("用户%s没有权限" % (current_user))

    return wrapper2

用这两个装饰器来装饰函数delete。

@is_login 
@is_permission
def delete():
    return "正在删除学生信息"

装饰过程是:
1). delete = is_permission(delete) # delete = wrapper2
先用装饰器 is_permission装饰delete函数,装饰完后,delete指向wrapper2
2). delete = is_login(delete) # delete = is_login(wrapper2) # delete = wrapper1
再用装饰器 is_login装饰delete函数(实质上是wrapper2),装饰完后,delete指向wrapper1

调用函数:

result = delete()
print(result)

被调用的过程是:
delete( ) ------> wrapper1( ) —> wrapper2( ) —> delete( )
执行delete( ),实际上是执行wrapper1( )的函数体,wrapper1中的fun参数是接收到的wrapper2函数,因此执行wrapper2;warpper2( )函数体中的fun参数是delete函数,执行完后,再一步一步将返回值带回。最后打印。
执行效果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.带参数的装饰器

如果装饰器需要传递参数, 在原有的装饰器外面嵌套一个函数即可。
直接看例子,假如我们这里对用户登陆有限制,只能本地用户登陆,不支持远程用户登录,可以通过装饰器接收参数,来分别执行。
以下为装饰器,注意要在外部嵌套一个函数

def auth(type):
    def wrapper1(fun):
        def wrapper(*args, **kwargs):
            if type=='local':
                user = input("User:")
                passwd = input("Passwd:")
                if user == 'root' and passwd == '123':
                    result = fun(*args, **kwargs)
                    return result
                else:
                    print("用户名/密码错误")
            else:
                print("暂不支持远程用户登录")

        return wrapper

    return wrapper1

用这个装饰器auth来装饰home函数

@auth(type='remote')
def home():
    print("这是主页")

这里要注意的是:上面我们语法糖的结构是@funName
这里的结构是
@funName()
。默认跟的不是函数名, 先执行改该函数, 获取函数返回结果,再 @funNameNew

一定要注意:
@函数名 add=函数名(add)
@函数名( ) @新的函数名
调用函数看效果:

home()

在这里插入图片描述
如果auth装饰器中的参数是local,执行结果为:
在这里插入图片描述
在这里插入图片描述
【官方装饰器案例】
https://wiki.python.org/moin/PythonDecoratorLibrary

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章