Python 装饰器

Python 装饰器

一、简单的装饰器

  • 1.为什么要使用装饰器

   装饰器的功能:在"不修改原函数"及其"调用方式"的情况下对原函数功能进行扩展

   装饰器的本质:就是一个闭包函数

  • 需求一:

    网站刚上线为了吸引客户群体,所有版块都可免费观看,可经过一段时间后宽带费给公司带来了很大的开销,公司领导决定对其中某些版块进行收费(会员登录可看)。

    因要对其某一版本进行认证,就把认证功能提取出来单独写个模块,然后直接调用即可。如下: 

# 用户登录了就把这个改成True
user_status = False
def login():
    """
    1.假装使用DB里用户信息
    :return:
    """
    _username = "Harry"
    _password = "abc123"
    # global 作用:如果用户登录成功就将函数外的"user_status"全局变量值修改为True
    global user_status

    if user_status == False:
        username = input("username:")
        password = input("password:")

        if username == _username and password == _password:
            print("welcome login....")
            user_status = True
        else:
            print("wrong username or password!")
    else:
        print("用户已登录,验证通过...")

def home():
    print("\033[05;34m-----首  页-----\033[0m")

def Movie():
    login()
    print("\033[02;36m-----电  影-----\033[0m")

def TV_series():
    print("\033[03;37m-----电视剧-----\033[0m")

def Entertainment():
    login()
    print("\033[06;38m-----娱  乐-----\033[0m")
home()
Movie()
TV_series()
Entertainment()

运行结果:
-----首  页-----
username:Harry
password:abc123
welcome login....
-----电  影-----
-----电视剧-----
用户已登录,验证通过...
-----娱  乐----- 

    这样写存在一个问题:代码是实现了功能,但是如果现在有很多模块都需要增加认证功能,就需要修改认证模块的代码。这违反了"开放-封闭"原则(简单说:已实现功能的代码不允许被修改,但允许被扩展

  • 封闭--已实现的功能代码快
  • 开放--对扩张开发  

    嗯。思考半天后想到了解决方案,可以使用高阶函数。就是把一个函数传给另外一个函数,直接把这个功能的函数名当做一个参数传给需要验证模块就行了 

user_status = False  # 用户登录了就把这个改成True
def login(func):  # 把要执行的模块从这里传进来  此处的func就相当于是login(Movie)【Movie】函数
    _username = "Harry"  # 假装这是DB里存的用户信息
    _password = "abc123"  # 假装这是DB里存的用户信息
    global user_status

    if user_status == False:
        username = input("user:")
        password = input("pasword:")

        if username == _username and password == _password:
            print("welcome login....")
            user_status = True
        else:
            print("wrong username or password!")

    if user_status == True:
        func()  # 看这里看这里,只要验证通过了,就调用相应功能.  就是上边传送过来的函数.
def home():
    print("\033[05;34m-----首  页-----\033[0m")

def Movie():
    print("\033[02;36m-----电  影-----\033[0m")

def TV_series():
    print("\033[03;37m-----电视剧-----\033[0m")

def Entertainment():
    print("\033[06;38m-----娱  乐-----\033[0m")

home()
# 需要验证就调用 login,把需要验证的功能 当做一个参数传给login
login(Movie)
TV_series()
login(Entertainment)

执行结果:
-----首  页-----
user:Harry
pasword:abc123
welcome login....
-----电  影-----
-----电视剧-----
-----娱  乐----- 
  • 按照之前反馈的问题进行修改后,又犯了一个大忌.

    修改了调用方式。想一想每个需要认证的模块,都必须调用login()方法。并把自己的认证方法给加上,之前不是这样调用的。试想如果100个模块都需要认证那这100个模块都得修改调用方式,这么多模块肯定都不是一个人写的,让每个人都修改调用方法才能加上认证,肯定会被骂死的。。

  • 解决思路:

    使用匿名函数【lambda】代码如下: 

#这样就相当于取了一个别名. 人家在调用Movie时就等会执行login(Movie).通过login里的验证后.就会自动调用Movie功能
login(Movie)		修改为:Movie = login(Movie)

#这样就相当于取了一个别名. 人家在调用Entertainment时就等会执行login(Entertainment).通过login里的验证后.就会自动调用Entertainment功能
login(Entertainment)修改为:Entertainment = login(Entertainment)

执行结果:
-----首  页-----
user:Harry
pasword:abc123
welcome login....
-----电  影-----
-----电视剧-----
-----娱  乐-----  
  • 但这样写存在一个问题: 

    用户在未调用时. Movie = login(Movie)就会先把自己给执行了.应该是等用户调用时程序才执行的.这样应该怎么做呢? 

    想实现一开始你写的Movie = login(Movie)不触发你函数的执行.只需要在这个login里面再定义一层函数.第一次调用america = login(america)只调用到外层login.这个login虽然会执行.但不会触发认证了.因为认证的所有代码被封装在login里层的新定义 的函数里了.login只返回 里层函数的函数名.这样下次再执行america()时.就会调用里层函数啦.....代码如下:  

  • 终结版  装饰器  普通版本
user_status = False  # 用户登录了就把这个改成True
def login(func):  # 把要执行的模块从这里传进来
    def inner():  # 再定义一层函数
        _username = "Harry"  # 假装这是DB里存的用户信息
        _password = "abc123"  # 假装这是DB里存的用户信息
        global user_status

        if user_status == False:
            username = input("user:")
            password = input("pasword:")

            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("wrong username or password!")

        if user_status == True:
            func()  # 看这里看这里,只要验证通过了,就调用相应功能

    return inner  # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
def home():
    print("\033[05;34m-----首  页-----\033[0m")

def Movie():
    print("\033[02;36m-----电  影-----\033[0m")

def TV_series():
    print("\033[03;37m-----电视剧-----\033[0m")

def Entertainment():
    print("\033[06;38m-----娱  乐-----\033[0m")

home()
Movie = login(Movie)  # 此处的Moive对应的是inner【return inner】的内存地址.  可以打印一下Movie看下是不是
print(Movie)
Movie()
TV_series()
Entertainment = login(Entertainment)
Entertainment()

执行结果:
-----首  页-----
<function login.<locals>.inner at 0x0000000000A72A60>
user:Harry
pasword:abc123
welcome login....
-----电  影-----
-----电视剧-----
-----娱  乐-----

  总结:  

    使用 1.使用嵌套函数. 2.高阶函数。  实现了.在不改变原有代码+原有调用方式的情况下. 给我的模块加了新功能.

    在开发中一个常用的玩法.叫做"语法糖". 官方叫"装饰器". 其实上边的代码还可以更简单.如下:  

Movie = login(Movie) #将这行代码去掉. 
  • 只需要你想装饰的函数上面加上@login. 如下代码:
  • 开发过程中都用装饰器时都使用该方法。如下

  完美解决 "装饰器"

user_status = False  # 用户登录了就把这个改成True
def login(func):  # 把要执行的模块从这里传进来
    def inner():  # 再定义一层函数
        _username = "Harry"    # 假装这是DB里存的用户信息
        _password = "abc123"  # 假装这是DB里存的用户信息
        global user_status

        if user_status == False:
            username = input("user:")
            password = input("pasword:")

            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("wrong username or password!")

        if user_status == True:
            func()  # 看这里看这里,只要验证通过了,就调用相应功能

    return inner  # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
def home():
    print("\033[05;34m-----首  页-----\033[0m")

@login
def Movie():
    print("\033[02;36m-----电  影-----\033[0m")

@login
def TV_series():
    print("\033[03;37m-----电视剧-----\033[0m")

def Entertainment():
    print("\033[06;38m-----娱  乐-----\033[0m")

home()
Movie()
TV_series()
Entertainment()

执行结果:
-----首  页-----
user:Harry
pasword:abc123
welcome login....
-----电  影-----
-----电视剧-----
-----娱  乐-----

  # 相当方便.想给谁加就给谁加. 相当于热插拔.  和上边的效果一样.并且这个更简单. 【 效果是一样的。】

装饰起作用:

  1.完全符合. 开放--封闭原则.

  2.不改变原有功能代码.不改变原有调用方式.

  3.实现扩展新功能  

二、传参数的装饰器

  • 传一位参数的装饰器
user_status = False  # 记录用户登录状态.默认未登录False  登录成功后.状态更改为True
def login(func):  # 把要执行的板块从这里传进来
    def inner(arg1):  # 再定义一层函数
        _username = "Harry"  # 假装这是DB里存的用户信息
        _password = "abc123"  # 假装这是DB里存的用户信息
        global user_status  # 如果用户登录成功就将函数外的user_status全局变量值修改为True

        if user_status == False:
            username = input("user:")
            password = input("pasword:")

            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("wrong username or password!")

        if user_status == True:
            func(arg1)  # 看这里看这里.只要验证通过了.就调用相应功能.  意思是用户登录成功.
    return inner  # 用户调用login时.只会返回inner的内存地址、下次再调用时加上()才会执行inner函数

def home():
    print("\033[05;34m-----首  页-----\033[0m")
@login
def Movie(style):
    print("\033[02;36m-----电  影--%s---\033[0m"%style)

def TV_series():
    print("\033[03;37m-----电视剧-----\033[0m")
@login
def Entertainment(style):
    print("\033[06;38m-----娱  乐--%s---\033[0m"%style)

home()
Movie("AA")
TV_series()
Entertainment("BB")

执行结果:
-----首  页-----
user:Harry
pasword:abc123
welcome login....
-----电  影--AA---
-----电视剧-----
-----娱  乐--BB---
  • 传多个参数的装饰器
user_status = False                 # 记录用户登录状态.默认未登录False  登录成功后.状态更改为True
def login(func):                    # 把要执行的板块从这里传进来
    def inner(*args,**kwargs):      # 再定义一层函数
        _username = "Harry"        # 假装这是DB里存的用户信息
        _password = "abc123"       # 假装这是DB里存的用户信息
        global user_status         # 如果用户登录成功就将函数外的user_status全局变量值修改为True

        if user_status == False:
            username = input("user:")
            password = input("pasword:")

            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("wrong username or password!")

        if user_status == True:
            func(*args,**kwargs)      # 看这里看这里.只要验证通过了.就调用相应功能.  意思是用户登录成功.
    return inner  # 用户调用login时.只会返回inner的内存地址、下次再调用时加上()才会执行inner函数

def home():
    print("\033[05;34m-----首  页-----\033[0m")
@login
def Movie(style):
    print("\033[02;36m-----电  影--%s---\033[0m"%style)

def TV_series():
    print("\033[03;37m-----电视剧-----\033[0m")
@login
def Entertainment(*args,**kwargs):
    print("\033[06;38m-----娱  乐--%s,%s---\033[0m"%(args,kwargs))

home()
Movie("AA")
TV_series()
Entertainment("AA","BB",9999)

执行结果:
-----首  页-----
user:Harry
pasword:abc123
welcome login....
-----电  影--AA---
-----电视剧-----
-----娱  乐--('AA', 'BB', 9999),{}---

  此时终于搞定了所有需求,完全遵循开放-封闭原则. 代码如上:

三、第三方登录的装饰器

现在有很多网站都允许第三方登录【原因:避免用户记录很多用户名和密码】.如QQ、weixing(微信)、weibo(微博)等.这...此时如何解决了.

代码如下:【不同板块允许不通的认证方式,多参数.支持第三方登录验证】

user_status = False     # 记录用户登录状态.默认未登录False  登录成功后.状态更改为True
def login(auth_type):
    print(auth_type)
    def auth(func):        # 把要执行的板块从这里传进来
        def inner(*args,**kwargs):        # 再定义一层函数
            if auth_type == "QQ":
                _username = "Harry"    # 假装这是DB里存的用户信息
                _password = "abc123"        # 假装这是DB里存的用户信息
                global user_status

                if user_status == False:
                    username = input("user:")
                    password = input("pasword:")

                    if username == _username and password == _password:
                        print("welcome login....")
                        user_status = True
                    else:
                        print("wrong username or password!")
                if user_status == True:
                    func(*args,**kwargs)  # 看这里看这里.只要验证通过了.就调用相应功能.  意思是用户登录成功.
            else:
                print("only support qq ")
        return inner   # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
    return auth
def home():
    print("\033[05;34m-----首  页-----\033[0m")
@login
def Movie(*args,**kwargs):
    print("\033[02;36m-----电  影--%s,%s---\033[0m"%(args,kwargs))
@login("QQ")
def TV_series(*args,**kwargs):
    print("\033[03;37m-----电视剧--%s,%s---\033[0m"%(args,kwargs))

def Entertainment():
    print("\033[06;38m-----娱  乐-----\033[0m")

home()
Movie("110")
TV_series("AA","BB")

执行结果:
<function Movie at 0x00000000011A2A60>
QQ
-----首  页-----
user:Harry
pasword:abc123
welcome login....
-----电视剧--('AA', 'BB'),{}---
posted @ 2018-12-08 17:35  HonGIm  阅读(170)  评论(0)    收藏  举报