zoukankan      html  css  js  c++  java
  • Python 基础之-装饰器

    定义

    本质就是函数,功能是装饰其它函数,其实就是给其它函数添加附加功能。

    原则

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

    2、不能修改被装饰的函数的调用方式

    其实就是当你用装饰器函数装饰其它函数的时候,被装饰的函数被装饰之前的调用方式不会有任何区别。

    实现装饰器的知识储备

    1、函数即 “变量”

    2、高阶函数

    3、嵌套函数

    高阶函数+嵌套函数 == 装饰器

    高阶函数

    什么是高阶函数?

    1、把一个函数名当做实参传给另外一个函数

    2、返回值中包含函数名

    下面看例子

    比如51cto网站上有以下版块

    def blog():
        print('欢迎来到 51cto 博客专区!')
    
    def forum():
        print('欢迎来到 51cto 论坛专区!')
    
    def college():
        print('欢迎来到 51cto 学院专区!')
    
    blog()
    forum()
    college()
    

      

    初期,这几个版块都可以免费访问,但是有些版块涉及到一些商业用途。如学院版块,里面有大量的视频教程可以商用。就需要付费,才能观看这些视频。为了实现这个需求,可以考虑让其进行用户认证,认证通过后,再判定这个用户是否是VIP付费会员就可以了。这个需求看似很简单,因为要对多个板块进行认证,应该把认证功能提取出来单独写个模块,然后每个模块调用就可以了。看下面代码:

    user_status =False     #用户登录状态
    username,password = 'tbb','123'     #假设这是DB里存的用户信息
    def login():
        global user_status
        if user_status == False:
            _username = input('输入用户名:')
            _password = input('输入密码:')
            if _username == username and _password == password:
                user_status = True     #登录成功就把状态改为True
                return True
            else:
                print('认证失败!')
                return False
        else:
            return True
    
    def blog():
        print('欢迎来到 51cto 博客专区!')
    
    def forum():
        status = login()     #执行验证
        if status:
            print('欢迎来到 51cto 论坛专区!')
    
    def college():
        status = login()     #执行验证
        if status:
            print('欢迎来到 51cto 学院专区!')
    
    blog()
    forum()
    college()
    

      

    上面的代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个 “开放-封闭” 的原则。简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

    • 封闭:已经实现的代码功能块不应该被修改
    • 开放:对现有功能的扩展开放

     要实现这个需求,那么就要用到高阶函数了,就是把一个函数当做一个参数传给另外一个函数,看下面的代码:

    user_status = False  # 用户登录状态
    username, password = 'tbb', '123'  # 假设这是DB里存的用户信息
    
    def login(func):     #把要执行的模块从这里传进来
        global user_status
        if user_status == False:
            _username = input('输入用户名:')
            _password = input('输入密码:')
            if _username == username and _password == password:
                user_status = True  # 登录成功就把状态改为True
                #return True
            else:
                print('认证失败!')
                #return False
        if user_status == True:
            #return True
            func()     #只要验证通过了,就调用相应功能
    
    def blog():
        print('欢迎来到 51cto 博客专区!')
    
    def forum():
        #status = login()  # 执行验证
        #if status:
        print('欢迎来到 51cto 论坛专区!')
    
    def college():
        #status = login()  # 执行验证
        #if status:
        print('欢迎来到 51cto 学院专区!')
    
    blog()
    #forum()
    login(forum)     #需要验证就调用login,把需要验证的功能当做一个参数传给login
    #college()
    login(college)
    

      

    上面的代码虽然在不改变源功能代码的前提下,给功能加上了验证,但是却改变了调用方式。想一想,现在每个需要认证的模块,都必须调用你的login()方法,并把自己的函数名传给你,这和之前的调用方式完全不一样。试想一下,在生产环境中,如果有100个模块需要认证,那这100个模块都得更改调用方式,这么多模块肯定不止是一个人写的,让每个人再去修改调用方式才能加上认证,这样会被骂死的。

    要实现这种需求,用嵌套函数加上高阶函数就可以实现,看下面的代码:

    user_status = False  # 用户登录状态
    username, password = 'tbb', '123'  # 假设这是DB里存的用户信息
    def login(func):     #把要执行的模块从这里传进来
    
        def inner():     #再定义一层函数
            global user_status
            if user_status == False:
                _username = input('输入用户名:')
                _password = input('输入密码:')
                if _username == username and _password == password:
                    user_status = True  # 登录成功就把状态改为True
                    #return True
                else:
                    print('认证失败!')
                    #return False
            if user_status == True:
                #return True
                func()
        return inner     #用户调用login时,返回的是inner的内存地址
    def blog():
        print('欢迎来到 51cto 博客专区!')
    
    def forum():
        print('欢迎来到 51cto 论坛专区!')
    
    def college():
        print('欢迎来到 51cto 学院专区!')
    
    blog()
    forum = login(forum)     #调用login函数,返回inner的内存地址,再赋值给forum变量,即forum = inner的内存地址
    forum()     #此时,执行forum()其实就是执行了inner(),inner函数中的func()才是装饰器要装饰的forum函数,即func()就是forum()
    #login(forum)
    college = login(college)
    college()
    #login(college)
    

      

    上面代码中的使用装饰器的方法并不是最好的,因为我们是执行了forum = login(forum),然后执行forum(),其实Python中给我们提供了一种方法,即在要装饰的函数上面添加@login,执行@login其实就是执行forum = login(forum),并且上面这种方式,还存在一个问题,就是当被装饰的函数有参数需要传递的时候,就不能用了,修改后的代码如下:

    user_status = False  # 用户登录状态
    username, password = 'tbb', '123'  # 假设这是DB里存的用户信息
    def login(func):     #把要执行的模块从这里传进来
    
        def inner(*args,**kwargs):     #再定义一层函数
            global user_status
            if user_status == False:
                _username = input('输入用户名:')
                _password = input('输入密码:')
                if _username == username and _password == password:
                    user_status = True  # 登录成功就把状态改为True
                    #return True
                else:
                    print('认证失败!')
                    #return False
            if user_status == True:
                #return True
                func(*args,**kwargs)
        return inner     #用户调用login时,返回的是inner的内存地址
    def blog():
        print('欢迎来到 51cto 博客专区!')
    @login     #就相当于 forum = login(forum)
    def forum(name):
        print('欢迎来到 51cto 论坛专区! %s' %name)
    @login     #就相当于 college = login(college)
    def college(name):
        print('欢迎来到 51cto 学院专区! %s' %name)
    
    blog()
    forum('tbb')
    college('tbb')
    无参装饰器

    上述代码还有一个小问题,就是如果被装饰的函数存在return返回值得时候,也是无法显示返回的数据,看下图

     

    修改后的代码如下所示:

    user_status = False  # 用户登录状态
    username, password = 'tbb', '123'  # 假设这是DB里存的用户信息
    def login(func):     #把要执行的模块从这里传进来
        def inner(*args,**kwargs):     #再定义一层函数
            global user_status
            if user_status == False:
                _username = input('输入用户名:')
                _password = input('输入密码:')
                if _username == username and _password == password:
                    user_status = True  # 登录成功就把状态改为True
                else:
                    print('认证失败!')
            if user_status == True:
                #return True
                res = func(*args,**kwargs)     #把forum()执行后的返回值赋值给res
                return res
        return inner     #用户调用login时,返回的是inner的内存地址
    def blog():
        print('欢迎来到 51cto 博客专区!')
    @login     #就相当于 forum = login(forum)
    def forum(name):
        print('欢迎来到 51cto 论坛专区! %s' %name)
        return 'from forum'
    
    @login     #就相当于 college = login(college)
    def college(name):
        print('欢迎来到 51cto 学院专区! %s' %name)
    
    blog()
    print(forum('tbb'))
    college('tbb')
    View Code

    但是上面的代码还不是最完整的,有一种情况上述的代码无法使用,就是在做用户认证的时候,如果有多个板块,要用不同的认证方式,即论坛专区用本地认证,学院专区用ldap来认证。即代码应该为:

    user_status = False  # 用户登录状态
    username, password = 'tbb', '123'  # 假设这是DB里存的用户信息
    def login(auth_type):     #把要执行的模块从这里传进来
        def auth(func):
            def inner(*args,**kwargs):     #再定义一层函数
                print('认证方式为:33[31;1m%s33[0m'%auth_type)
                global user_status
                if user_status == False:
                    _username = input('输入用户名:')
                    _password = input('输入密码:')
                    if _username == username and _password == password:
                        user_status = True  # 登录成功就把状态改为True
                    else:
                        print('认证失败!')
                if user_status == True:
                    res = func(*args,**kwargs)     #把forum()执行后的返回值赋值给res
                    return res
            return inner     #用户调用auth时,返回的是inner的内存地址
        return auth     #用户调用login时,返回的是auth的内存地址
    def blog():
        print('欢迎来到 51cto 博客专区!')
    @login(auth_type='local')     #当添加@login(auth_type='local')的时候是先执行login(auth_type='local'),其实就是执行login方法,这时候返回auth的内存地址,即@login(auth_type='local')变成了@auth,而@auth就相当于forum = auth(forum),即forum = inner,这样当后面调用forum()其实就是调用inner()
    def forum(name):
        print('欢迎来到 51cto 论坛专区! %s' %name)
        return 'from forum'
    
    @login(auth_type='ldap')
    def college(name):
        print('欢迎来到 51cto 学院专区! %s' %name)
    
    blog()
    print(forum('tbb'))
    college('tbb')
    带参装饰器
  • 相关阅读:
    自己第一个github开源的感受
    直播时代--IOS直播客户端SDK,美颜直播【开源】
    OpenCL / OpenGL / OpenAL
    FFmpeg 라이브러리 코덱과 영상 변환을 중심으로
    nginx + http2.0 解决浏览器跨域和同源限制问题
    软件编译系统构建
    SRS支持rtmp/srt/gb28181/webrtc上行推流和rtmp/http-flv/hls/dash/gb28181/webrtc下行拉流
    SIP UA/UAC/UAS/GB28181-Server/GB28181-Client 五合一
    SUPL(安全用户面定位)
    RTMP低延时配置
  • 原文地址:https://www.cnblogs.com/cyfiy/p/7704866.html
Copyright © 2011-2022 走看看