python装饰器

Posted 每天进步一点点

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python装饰器相关的知识,希望对你有一定的参考价值。

python装饰器

假如你是一家视频网站的后端开发工程师,你们网站有以下几个版块:

def Home():
    print("-"*10+"首页"+"-"*10)

def America():
    print("-"*10+"欧美专区"+"-"*10)

def Japan():
    print("-"*10+"日本专区"+"-"*10)

def Beijing():
    print("-"*10+"北京专区"+"-"*10)

在视频刚上线初期,为了吸引客户,你们采取了免费政策,所有视频免费观看,迅速吸引了一大批用户,免费一段时间后,发现每天巨大的带宽费用公司承受不起,所以准备对比较受欢迎的几个版块进行收费,其中包括“欧美”和“北京”专区,拿到这个需求后,想了想,想收费就需要对用户进行认证,认证通过后,再判断这个用户是否是VIP付费会员就可以了,是VIP就让他观看,不是VIP就不让他观看呗!你觉得这个需求很简单,因为要对多个版块进行认证,那应该把认证功能提取出来单独写个模块,然后每个版块里调用就可以了,于是你轻轻松松的实现了下面的功能:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2018/5/7 15:21
# @Author  : XiaoYafei
# @File    : 装饰器.py

user_status = False  # 用户登陆了就把这个改成True

def login():
    _username = "admin"  # 假如这是数据库里存的用户信息
    _password = ‘123‘  # 假如这是数据库里存的用户信息
    global user_status  # 声明全局变量

    if user_status == False:
        username = input("请输入你的姓名:")
        password = input("请输入你的密码:")

        if username == _username and password == _password:  # 如果输入的是相同的
            print("Welcome Login...")
            user_status = True
        else:
            print("用户名或密码错误...")

    if user_status == True:
        print("用户已登陆,验证通过...")



def Home():
    print("-"*10+"首页"+"-"*10)

def America():
    login()  # 执行前加上验证
    print("-"*10+"欧美专区"+"-"*10)

def Japan():
    print("-"*10+"日本专区"+"-"*10)

def Beijing():
    login()  # 执行前加上验证
    print("-"*10+"北京专区"+"-"*10)


America()  # 调用欧美专区
Beijing()  # 调用北京专区

代码执行结果:

请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------欧美专区----------
用户已登陆,验证通过...
----------北京专区----------

此时你信心满满的把这个代码提交给你的TEAM LEADER审核,没成想,没过5分钟,代码就被打回来了, TEAM LEADER给你反馈是,我现在有很多模块需要加认证模块,你的代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个原则“开放-封闭”原则,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

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

这个原则你还是第一次听说,再次感受到了野生程序员和正规军的差距,可是,老大要求的实现要如何实现呢?如何在不该原有功能的情况下加上认证功能呢?你一时想不出来思路,只好带着这个问题回家继续写,媳妇不在家,去隔壁老王家串门了,你正好落得清静,一不小心就想到了解决方案,不改源代码可以实现啊。

你肯定知道什么是高阶函数,就是把一个函数当作一个参数传递给另一个函数,那么,我只需要写一个认证方法,每次调用需要验证的功能时,直接把这个函数当作一个参数传给这个验证模块不就行了吗?哈哈,机智如我,于是你啪啪啪改写了之前的代码:

user_status = False  # 用户登陆了就把这个改成True

def login(func):
    _username = "admin"  # 假如这是数据库里存的用户信息
    _password = ‘123‘  # 假如这是数据库里存的用户信息
    global user_status  # 声明全局变量

    if user_status == False:
        username = input("请输入你的姓名:")
        password = input("请输入你的密码:")

        if username == _username and password == _password:  # 如果输入的是相同的
            print("Welcome Login...")
            user_status = True
        else:
            print("用户名或密码错误...")

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



def Home():
    print("-"*10+"首页"+"-"*10)

def America():
    print("-"*10+"欧美专区"+"-"*10)

def Japan():
    print("-"*10+"日本专区"+"-"*10)

def Beijing():
    print("-"*10+"北京专区"+"-"*10)

login(America)  # 需要验证就调用loing,把需要验证的功能当作一个参数传递给login
login(Beijing)

你明白我想传达什么意思了么? 你:......好像不太明白。 老王:好吧,那我在给你点一下,你之前写的下面这段调用认证的代码

login(America)  
login(Beijing)

你之所以修改了调用方式,是因为用户每次调用时需要执行login(America)类似的,其实只需要稍微改改就可以了:

America = login(America)  
Beijing = login(Beijing)

这样你,其它人调用Beijing时,其实相当于调用了login(Beijing),通过login里的验证后,就会自动调用Beijing功能。 你:我擦,还真是唉。。。,老王,还是你nb。。。不过,等等,我这样写了好,那用户调用时,应该是下面这个样子:

America = login(America)  
Beijing = login(Beijing)
America()
Beijing()

那么此时问题又来了,America = login(America)即先执行完右边的login(America)然后把结果赋值给了左边的America,即你还没有调用,程序自己就执行了,那么这个代码应该等我用户登陆的时候才执行对吧,不信我试给你看:

user_status = False  # 用户登陆了就把这个改成True

def login(func):
    _username = "admin"  # 假如这是数据库里存的用户信息
    _password = ‘123‘  # 假如这是数据库里存的用户信息
    global user_status  # 声明全局变量

    if user_status == False:
        username = input("请输入你的姓名:")
        password = input("请输入你的密码:")

        if username == _username and password == _password:  # 如果输入的是相同的
            print("Welcome Login...")
            user_status = True
        else:
            print("用户名或密码错误...")

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



def Home():
    print("-"*10+"首页"+"-"*10)

def America():
    print("-"*10+"欧美专区"+"-"*10)

def Japan():
    print("-"*10+"日本专区"+"-"*10)

def Beijing():
    print("-"*10+"北京专区"+"-"*10)

America = login(America)  # 需要验证就调用loing,把需要验证的功能当作一个参数传递给login
Beijing = login(Beijing)

运行结果如下:

请输入你的姓名:

果然老王说的是对的,根本就没有去调用,这个代码就自动执行了,这时你说:这个问题应该怎么解决呢?老王问:你知道嵌套函数吗?你回答:知道。老王说:想实现一开始你写的america = login(america)不触发你函数的执行,只需要在这个login里面再定义一层函数,第一次调用america = login(america)只调用到外层login,这个login虽然会执行,但不会触发认证了,因为认证的所有代码被封装在login里层的新定义 的函数里了,login只返回 里层函数的函数名,这样下次再执行america()时, 就会调用里层函数啦,你说:...这是什么意思?我一脸懵逼。老王说:算了,还是给你看代码吧:

user_status = False  # 用户登陆了就把这个改成True

def login(func):

    def inner():
        _username = "admin"  # 假如这是数据库里存的用户信息
        _password = ‘123‘  # 假如这是数据库里存的用户信息
        global user_status  # 声明全局变量

        if user_status == False:
            username = input("请输入你的姓名:")
            password = input("请输入你的密码:")

            if username == _username and password == _password:  # 如果输入的是相同的
                print("Welcome Login...")
                user_status = True
            else:
                print("用户名或密码错误...")

        if user_status == True:
            func()  # 只要验证通过了,就调用相应功能
    return inner   # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数



def Home():
    print("-"*10+"首页"+"-"*10)

def America():
    print("-"*10+"欧美专区"+"-"*10)

def Japan():
    print("-"*10+"日本专区"+"-"*10)

def Beijing():
    print("-"*10+"北京专区"+"-"*10)

America = login(America)  # 需要验证就调用loing,把需要验证的功能当作一个参数传递给login
Beijing = login(Beijing)
print(America)  # <function login.<locals>.inner at 0x0000020DFEAB91E0>
America()
Beijing()

让我们来看一下代码的执行顺序:

  • 1.程序直接到了最下边America = login(America),调用了login,并把函数当作参数传递进去;
  • 2.在login函数内部,程序从上到下执行,首先执行inner(),在inner()函数里做了认证;
  • 3.inner()函数执行完,返回inner函数,此时return inner即代表返回的是inner函数的内存地址;
  • 4.在代码的底部,America = login(America),login(America)返回的是inner的内存地址;
  • 5.我们刚刚说:如果打印的是函数名,则是函数的内存地址,如果函数名加上(),那么就是执行函数;
  • 6.所以America = login(America)此时左边的America代表的是inner函数的内存地址,我们打印后可以看到;
  • 7.America()执行innera函数;

此时问题暂时得到解决,在刚刚的代码中,有人会说,returni inner表示返回一个值代表着程序的终止,那么为什么我还能运行America()呢?那么我们就需要了解一下闭包了:

闭包

关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问他们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含他们外部函数之外被调用时,就会形成闭包。

也就是说:内部函数会在外部w函数执行后返回。而当这个函内部函数执行时,它仍然必须访问其外部函数的局部、参数以及其他内部的函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回的值,但也会受到内部函数的影响。

代码如下:

def func():
    n = 10

    def func2():
        print("func2",n)

    return func2  # 返回func2 的内存地址

res = func()  # 想要接收函数内部的返回值,就需要一个变量来接收
print(res)  # <function func.<locals>.func2 at 0x0000018E29200C80>返回的是func2的内存地址
res()  # func2 10

在函数里又定义了一层子函数,子函数被返回了,就是在外层函数执行的时候,返回了子函数的内存地址,在外面执行子函数的时候,又引用了外层函数这个变量,这种现象称之为:闭包。

等等,刚刚视频网站还没有完结......

你仔细看了老王的代码,觉得老王真的不是一般人呀,这种姿势很牛逼呀,你独创的吗?

此时你的媳妇嗤嗤的笑出声来,你也不知道她笑个球

老王说:呵呵,这不是我独创的,这是开发中一个常见的语法,叫语法糖,官方名称“装饰器”,其实上面的语法,还可以更简单:

user_status = False  # 用户登陆了就把这个改成True

def login(func):

    def inner():
        _username = "admin"  # 假如这是数据库里存的用户信息
        _password = ‘123‘  # 假如这是数据库里存的用户信息
        global user_status  # 声明全局变量

        if user_status == False:
            username = input("请输入你的姓名:")
            password = input("请输入你的密码:")

            if username == _username and password == _password:  # 如果输入的是相同的
                print("Welcome Login...")
                user_status = True
            else:
                print("用户名或密码错误...")

        if user_status == True:
            func()  # 只要验证通过了,就调用相应功能
    return inner  # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数



def Home():
    print("-"*10+"首页"+"-"*10)

@login  # 只需要在装饰的函数上面加上@login,就代表着会自动把下面的America这个函数名传递进来,成为loing(America),然后赋值给America,相同于:America = login(America)
def America():
    print("-"*10+"欧美专区"+"-"*10)

def Japan():
    print("-"*10+"日本专区"+"-"*10)

@login
def Beijing():
    print("-"*10+"北京专区"+"-"*10)

America()
Beijing()

效果是一样的。

你开心的玩着老王交给你的新姿势,玩着玩着就给你的北京专区模块加了个参数,然后,结果就出错了...

@login
def Beijing(style):
    print("-"*10+"北京专区"+"-"*10,style)

America()
Beijing("3P")

你说:老王,怎么传递一个参数就不行了呢?

老王回答:那是肯定的呀,你调用Beijing的时候,其实是相当于调用的login,你的Beijing第一次调用时Beijing = login(Beijing),返回的是inner的内存地址,第二次用户自己调用Beijing("3P"),实际上相当于调用的是inner,但你的inner定义的时候并没有设置参数,但你给它传递了一个参数,所以这就报错了,你说对不对?

你说:但是我的版块需要传参数啊,你不让我传不行啊!

老王回答:没说不让你传,稍作改动即可!

user_status = False  # 用户登陆了就把这个改成True

def login(func):  # 把要执行的模块从这里传递进来

    def inner(arg1):  # 再定义一层函数,并且接收传递进来的参数
        _username = "admin"  # 假如这是数据库里存的用户信息
        _password = ‘123‘  # 假如这是数据库里存的用户信息
        global user_status  # 声明全局变量

        if user_status == False:
            username = input("请输入你的姓名:")
            password = input("请输入你的密码:")

            if username == _username and password == _password:  # 如果输入的是相同的
                print("Welcome Login...")
                user_status = True
            else:
                print("用户名或密码错误...")

        if user_status == True:
            func(arg1)  # 只要验证通过了,就调用相应功能
    return inner  # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数



def Home():
    print("-"*10+"首页"+"-"*10)

@login
def America():
    print("-"*10+"欧美专区"+"-"*10) 

def Japan():
    print("-"*10+"日本专区"+"-"*10)

@login
def Beijing(style):  # 调用时传递参数
    print("-"*10+"北京专区"+"-"*10,style)

# America()
Beijing(‘3P‘)

运行结果如下:

请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------北京专区---------- 3P

那么,问题又来了,我有的版块没有其他的频道,那么我就不用传递参数,但是如果不传递参数就会报错,怎么办?这时候我们绞尽脑汁,传一个参数可以,不传参数也可以,眼睛一亮,非固定参数!

user_status = False  # 用户登陆了就把这个改成True

def login(func):  # 把要执行的模块从这里传递进来

    def inner(*args,**kwargs):  # 再定义一层函数
        _username = "admin"  # 假如这是数据库里存的用户信息
        _password = ‘123‘  # 假如这是数据库里存的用户信息
        global user_status  # 声明全局变量

        if user_status == False:
            username = input("请输入你的姓名:")
            password = input("请输入你的密码:")

            if username == _username and password == _password:  # 如果输入的是相同的
                print("Welcome Login...")
                user_status = True
            else:
                print("用户名或密码错误...")

        if user_status == True:
            # 如果加上*程序就不会报错,如果不加*,那么就代表打印的是列表和字典两个对象,但是Beijing只需要一个参数,却传进来了两个参数,包括:空列表,空字典,使用*代表不会当作真的参数去存放
            func(*args,**kwargs)  # 只要验证通过了,就调用相应功能
    return inner  # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数



def Home():
    print("-"*10+"首页"+"-"*10)

@login
def America():
    print("-"*10+"欧美专区"+"-"*10)

def Japan():
    print("-"*10+"日本专区"+"-"*10)

@login
def Beijing(style):
    print("-"*10+"北京专区"+"-"*10,style)

America()
Beijing(‘3P‘)

运行结果如下:

请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------欧美专区----------
----------北京专区---------- 3P

老王:你再试试就可以了

你:果然好使,大神就是大神啊......

老王说:这种姿势多练练,我就先回去了

你的媳妇为了不让打扰你,提出去她的好姐妹家过夜,你觉得你的媳妇真体贴,最终你搞定了所有需求,完全遵守封闭-开放原则,此时你累的已经不行了,洗洗就抓紧睡了,半夜,上厕所,隐隐听到隔壁老王家有微弱的女人的声音传来,你会心一笑,老王这家伙,不声不响找了女朋友也不带给我看看,改天一定要见下真人。

第二2天早上,产品经理又提了新的需求,要允许用户选择用qq\weibo\weixin认证,此时的你,已深谙装饰器各种装逼技巧,轻松的就实现了新的需求。

user_status = False  # 用户登陆了就把这个改成True

def login(auth_type):  # 把要执行的模块从这里传递进来

    def outer(func):
        def inner(*args,**kwargs):  # 再定义一层函数
            _username = "admin"  # 假如这是数据库里存的用户信息
            _password = ‘123‘  # 假如这是数据库里存的用户信息
            global user_status  # 声明全局变量

            if user_status == False:
                username = input("请输入你的姓名:")
                password = input("请输入你的密码:")

                if username == _username and password == _password:  # 如果输入的是相同的
                    print("Welcome Login...")
                    user_status = True
                else:
                    print("用户名或密码错误...")

            if user_status == True:
                # 如果加上*程序就不会报错,如果不加*,那么就代表打印的是列表和字典两个对象,但是Beijing只需要一个参数,却传进来了两个参数,包括:空列表,空字典,使用*代表不会当作真的参数去存放
                func(*args,**kwargs)  # 只要验证通过了,就调用相应功能
        return inner  # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
    return outer



def Home():
    print("-"*10+"首页"+"-"*10)

@login
def America():
    print("-"*10+"欧美专区"+"-"*10)

def Japan():
    print("-"*10+"日本专区"+"-"*10)

@login(‘qq‘)
def Beijing(style):
    print("-"*10+"北京专区"+"-"*10,style)

Beijing(‘3P‘)

代码运行结果如下:

请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------北京专区---------- 3P

为了让大家能够看懂,一步一步来:

以上是关于python装饰器的主要内容,如果未能解决你的问题,请参考以下文章

[TimLinux] Python 装饰器

python装饰器

python装饰器关键代码

Python装饰器

python之装饰器

python 装饰器