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')
带参装饰器
原文地址:https://www.cnblogs.com/cyfiy/p/7704866.html