[Python学习日记-40] 函数进阶之装饰器

news2024/11/26 11:59:56

[Python学习日记-40] 函数进阶之装饰器

简介

引子

什么是装饰器

装饰器终结版

装饰器的层层叠加

简介

        在前面铺垫了这么多终于该讲到重点了,前面说的匿名函数、高阶函数、闭包等等都是为了这篇文章所讲的装饰器而使用的,本篇文章将会一一个故事通俗易懂的说明什么是装饰器,那下面我们一起来看看到底什么是装饰器吧

引子

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

def home():
    print("---首页----")

def america():
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

def guangdong():
    print("----广东专区----")

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

account = {
    "is_authenticated":False, # 用户登录了就把这个改成True
    "username":"jove", # 假装这是DB里存的用户信息
    "password":"abc123" # 假装这是DB里存的用户信息
}

def login():
    if account["is_authenticated"] is False:
        username = input("user:")
        password = input("pasword:")
        if username == account["username"] and password == account["password"]:
            print("welcome login....")
            account["is_authenticated"] = True
        else:
            print("wrong username or password!")
    else:
        print("用户已登录,验证通过...")

def home():
    print("---首页----")

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

def japan():
    print("----日韩专区----")

def guangdong():
    login()    # 执行前加上验证
    print("----广东专区----")

home()
america()
guangdong()

 代码输出如下:

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

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

        作为非科班出身的你对于这个原则是第一次听说,但是不要紧,听到看到就是学到了,那现在应该如何实现这一要求呢?如何在不改原有功能代码的情况下加上认证功能呢?你一时间想不出思路,于是就打开了 CSDN 看起了 JoveZou 写的文章,文章通俗易懂,思路开阔,看着看着一不小心就想到了解决方案。不改源代码可以呀,这个时候可以用前两天在文章里看到的高阶函数(把一个函数当作一个参数传给另一个函数)来解决呀,总算有一天用上它了。这时我只需要写个认证方法,每次调用需要验证的功能时,直接把这个功能的函数名当做一个参数传给我的验证模块不就行了吗,思路弄清楚之后,你噼里啪啦的敲起键盘改写了之前的代码,代码如下

account = {
    "is_authenticated":False, # 用户登录了就把这个改成True
    "username":"jove", # 假装这是DB里存的用户信息
    "password":"abc123" # 假装这是DB里存的用户信息
}

def login(func):
    if account["is_authenticated"] is False:
        username = input("user:")
        password = input("pasword:")
        if username == account["username"] and password == account["password"]:
            print("welcome login....")
            account["is_authenticated"] = True
            func()
        else:
            print("wrong username or password!")
    else:
        print("用户已登录,验证通过...")

def home():
    print("---首页----")

def america():
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

def guangdong():
    print("----广东专区----")

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

代码输出如下:

        你看了看输出结果,很开心,心想终于实现了老板的要求了,不改变原功能代码的前提下,给功能加上了验证。

什么是装饰器

        此时你旁边的同事老李刚刚办完事乐呵呵的回到工位上,因为上次他帮了你的大忙,而且你同事还是当年呲碴风云的代码界“浩南哥”,于是你跟他分享了你刚写好的代码,你兴奋的等他看完,等着他夸奖你 NB,没成想老李看完后并没有夸你,而是转过身去笑笑说:“你这个代码还是改改吧,要不然会被开除的。”

        你一脸震惊,这明明实现了功能了呀,老李补充到:“没错,你功能是实现了,但是你犯了一个大忌!你改变了调用方式!想一想,现在每个需要认证的模块都必须调用你的 login() 方法,并把自己的函数名传给你,但是人家之前可不是这么调用的,试想一下,如果有100个模块需要认证,那这100个模块都得更改调用方式,这么多模块肯定不止是一个人写的,让每个人再去修改调用方式才能加上认证功能,你会被这些人骂死的。” 

        你觉得老李说的没错,但是如何即不改变原功能代码,又不改变原有调用方式,还能加上认证呢?你苦思了一会,还是想不出,于是你谦虚的请教了一下老李:“快给我点思路,实在想不出来了。”

        老李背对着你问:“学过匿名函数没有?”

        你:“学过学过,就是 lambda 嘛。”

        老李:“那 lambda 与正常函数的区别是什么?”

        你:“最直接的区别是,正常函数定义时需要写名字,但 lambda 不需要。”

        老李:“没错,那 lambda 定好后,为了多次调用,可否也给它命个名?”

        你:“可以呀,可以写成 plus = lambda x:x+1 类似这样,以后再调用plus就可以了。但这样不就失去了 lambda 的意义了,明明人家叫匿名函数呀,你起了名字有什么用呢?”

        老李:“我不是要跟你讨论它的意义,我想通过这个让你明白一个事实。”

        老李让你靠边挪挪,做到了你的位置上,写下了下面的代码

def plus(n):
    return n+1

plus2 = lambda x:x+1

        老李:“上面两种写法是不是都代表了同样的意思?”

         你点了点头,并嗯了一声,老李继续说道:“我给 lambda x:x+1 起了个名字叫 plus2,是不是相当于 def plus2(x)?”

        你恍然大悟的说道:“你别说还真是,但老李呀,你想说明什么呢?”

        老李:“没啥,只想告诉你,给函数赋值变量名就像 def func_name 是一样的效果的,例如 plus(n) 函数,你调用时可以用 plus 名,甚至还可以再给它起个其它名字,我直接演示一下吧。”于是老李飞快地敲起了代码,没一会就写出了下面的代码

def plus(n):
    return n+1

print(plus(4))
calc = plus
print(calc(4))

代码输出如下:

        看着这输出结果,你好像悟道了点什么但是又感觉还是不太明白,老李看着你疑惑的表情,于是他决定再给你点拨一下,于是打开了你之前的代码稍微改了一下,代码如下

account = {
    "is_authenticated":False, # 用户登录了就把这个改成True
    "username":"jove", # 假装这是DB里存的用户信息
    "password":"abc123" # 假装这是DB里存的用户信息
}

def login(func):
    if account["is_authenticated"] is False:
        username = input("user:")
        password = input("pasword:")
        if username == account["username"] and password == account["password"]:
            print("welcome login....")
            account["is_authenticated"] = True
            func()
        else:
            print("wrong username or password!")
    else:
        print("用户已登录,验证通过...")

def home():
    print("---首页----")

def america():
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

def guangdong():
    print("----广东专区----")

home()
america = login(america)
guangdong = login(guangdong)

代码输出结果:

        看到了输出结果,你疑惑的发现,怎么我还没有调用就已经执行了?你再次疑惑的望向了老李,老李仿佛预知了你的疑惑,他解释道:“先别着急,想要继续下一步要先弄懂这一步的奥秘,代码中很明显是想把 login 的内存地址传给 america 和 guangdong,这样别人如果想要调用的话直接在后面加括号就可以使用了,可是不完美的是,在别人调用之前就已经进行调用了。如果后面再使用 america() 和 guangdong() 来进行调用的话会抛出 TypeError: 'NoneType' object is not callable 的错误。”

        你:“老李,你是不是耍我呀,这明显也不行!”

        老李:“你先别着急,我都还没说完。调用上面已经符合要求了,那接下来我们要进行调整的就是 login() 里面的了,之所以会直接调用了是因为 america = login(america) 时直接就运行了 login() 内的代码了,这个时候就要用到闭包了。”老李又飞快的改起了代码,代码如下

account = {
    "is_authenticated":False, # 用户登录了就把这个改成True
    "username":"jove", # 假装这是DB里存的用户信息
    "password":"abc123" # 假装这是DB里存的用户信息
}

def login(func):
    def inner():
        if account["is_authenticated"] is False:
            username = input("user:")
            password = input("pasword:")
            if username == account["username"] and password == account["password"]:
                print("welcome login....")
                account["is_authenticated"] = True
                func()
            else:
                print("wrong username or password!")
        else:
            print("用户已登录,验证通过...")
    return inner

def home():
    print("---首页----")

def america():
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

def guangdong():
    print("----广东专区----")

home()
america = login(america)    # 这次执行 login 返回的是 inner 的内存地址
guangdong = login(guangdong)
america()

代码输出如下:

        你看到输出后非常震惊,只需要这么简单的两行代码就完成了?!于是你对老李非常佩服的竖起了大拇指,但老李居然把你的手压了下去,说还没完呢。你很疑惑,这不是已经实现了吗,也没有报错。这个时候老李缓缓说道:“刚刚我们所写的其实就是装饰器,但是我们刚刚所写的都是一些小白写法,如果就这样交上去你还是会被领导一顿臭批,其实还有简写的写法,这也是官方推荐的。”

        你:“那应该怎么写呢?”

        老李:“只需要在需要登录的版块前面加 @login(login 是扩展的模块)就可以了,那我就演示一下给你看看吧。”于是老李又噼里啪啦的一顿敲键盘,写下了下面的代码

account = {
    "is_authenticated":False, # 用户登录了就把这个改成True
    "username":"jove", # 假装这是DB里存的用户信息
    "password":"abc123" # 假装这是DB里存的用户信息
}

def login(func):
    def inner():
        if account["is_authenticated"] is False:
            username = input("user:")
            password = input("pasword:")
            if username == account["username"] and password == account["password"]:
                print("welcome login....")
                account["is_authenticated"] = True
                func()
            else:
                print("wrong username or password!")
        else:
            print("用户已登录,验证通过...")
    return inner

def home():
    print("---首页----")

@login # america = login(america)的简写方法
def america():
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

@login # guangdong = login(guangdong)的简写方法
def guangdong():
    print("----广东专区----")

home()
america()

代码输出如下:

        你一看,输出结果,果然和之前写的一样,并且代码变得更加简洁了,心想老李果然 NB 不愧是当年的代码界“浩南哥”,这个时候老李继续说道:“其实这就是我们常说的 Python 中的装饰器了,也叫做语法糖或糖衣语法,英文名叫 Syntactic sugar,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,把装饰器比作糖衣是不是很贴切呢?他就想一层糖衣一样裹在原有的代码外面。其实这只是最基本的装饰器...”

        你正听着老李的介绍,突然间你的手机响了一下...

装饰器终结版

        你拿起手机一看,是产品部门发来的消息,新的需求要求 VIP 等级达到3级以上才可以看“广东专区”,你看着新的需求思考了一会,用着老李教你的新姿势,在“广东专区”加了个新参数,结果报错了...

        这时你赶紧找到了老李问道:“老李,老李,怎么传个参数就不行了呢?”

        老李:“那必然呀,你调用 guangdong 时,其实是相当于调用的 login,你的 guangdong 第一次调用时 guangdong = login(guangdong),login 就返回了 inner 的内存地址,第二次用户自己调用 guangdong(5),实际上相当于调用的是 inner,但你的 inner 定义时并没有设置参数,但你给他传了个参数,所以自然就报错了呀。”

        你:“但是现在版块需要传参数啊,这样也不行呀...”

        老李:“也不是不让你传,只不过要稍做些改动才行。”于是老李又噼里啪啦的敲着键盘,代码如下

        试了一下果真好使,真想着感慨老李的姿势高超,花样繁多时,你突然想到,如果需要传输多个参数那应该怎么办呢?于是你再次向老李提问。

        老李皱了皱眉说道:“老弟,不是什么都要我教你吧,非固定参数(*args,**kwargs)你没学过吗?”

        你:“还能这么搞!NB 我再试试。”最终,你终于搞定了所有需求,完全遵循“开放-封闭”原则,最终代码如下

account = {
    "is_authenticated":False, # 用户登录了就把这个改成True
    "username":"jove", # 假装这是DB里存的用户信息
    "password":"abc123" # 假装这是DB里存的用户信息
}

def login(func):
    def inner(*args, **kwargs):
        if account["is_authenticated"] is False:
            username = input("user:")
            password = input("pasword:")
            if username == account["username"] and password == account["password"]:
                print("welcome login....")
                account["is_authenticated"] = True
                func(*args, **kwargs)
            else:
                print("wrong username or password!")
        else:
            print("用户已登录,验证通过...")
    return inner

def home():
    print("---首页----")

@login # america = login(america)的简写方法
def america():
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

@login # guangdong = login(guangdong)的简写方法
def guangdong(grade):
    if grade > 3:
        print("解锁本专区所有高级功能")
    else:
        print("----广东VIP专区----")

home()
guangdong(5)
america()

代码输出如下:

        看着领导空空的工位,不知道他何时偷偷溜走了,于是你兴高采烈的就收拾好东西下班去了,想着明天再交给领导。翌日,你精精神抖擞到了公司,把代码扔给领导,领导看完后,会心一笑说道:“小伙子不错,这才是专业的写法嘛”。

 

装饰器的层层叠加

        没过几天,产品部门又想出了新点子叫着要开会,会议上产品部门想着“广东VIP专区”除了要等级达到3级以上外,还需要再充1000元才能开通。开完会后你坐在自己的工位上陷入了沉思,望了望老李的工位,他今天刚好休假,这该怎么办好呢?突然想起前两天看到的一篇 JoveZou 的博客提到装饰器是可以层层叠加的,于是你就决定要尝试一下用装饰器的叠加来解决该问题,于是你飞快的写下了下面的代码

account = {
    "is_authenticated":False, # 用户登录了就把这个改成True
    "is_paid":False, # 假装是从DB获取的付款记录
    "username":"jove", # 假装这是DB里存的用户信息
    "password":"abc123" # 假装这是DB里存的用户信息
}

def login(func):
    def inner(*args,**kwargs): # 闭包
        if account["is_authenticated"] is False:
            username = input("user:")
            password = input("pasword:")
            if username == account["username"] and password == account["password"]:
                print("welcome login....")
                account["is_authenticated"] = True
                func(*args,**kwargs) # 认证成功,执行功能函数,使用到外部函数的func参数
            else:
                print("wrong username or password!")
        else:
            print("用户已登录,验证通过...")
            func(*args,**kwargs)  # 认证成功,执行功能函数
    return inner

def pay_money(func):
    def inner(*args,**kwargs):
        if account["is_authenticated"] is False and account["is_paid"] is False:
            print("this part need pay 1000 yuan.")
            print("no login,pleace input username and password to pay.")
            username = input("user:")
            password = input("pasword:")
            if username == account["username"] and password == account["password"]:
                print("pay success...")
                account["is_authenticated"] = True
                account["is_paid"] = True
                func(*args,**kwargs)
            else:
                print("wrong username or password!")
        elif account["is_authenticated"] is True and account["is_paid"] is False:
            print("this part need pay 1000 yuan.")
            print("user is online,please input password to pay.")
            password = input("pasword:")
            if password == account["password"]:
                print("pay success...")
                account["is_paid"] = True
                func(*args,**kwargs)
            else:
                print("wrong password!")
        else:
            print("user is online and paid.")
            func(*args, **kwargs)
    return inner

def home():
    print("---首页----")

@login # america = login(america)的简写方法
def america():
    print("----欧美专区----")

def japan():
    print("----日韩专区----")

@pay_money # login = pay_money(login)
@login # guangdong = login(guangdong)的简写方法,装饰器、语法糖
def guangdong(vip_level):
    if vip_level > 3:
        print("解锁本专区所有高级功能")
    else:
        print("----广东VIP专区----")

home()
america() # 相当于执行inner()
guangdong(4)

代码输出如下:

        你在原有的代码基础上新加了一个 pay_money(),并且用装饰器加在“广东VIP专区”,你看着自己写出来的代码,又看看了输出,确认这就是你要的,很快在下班前你就交给了你的领导,领导看到后非常满意,并竖起了大拇指表扬你,说你的代码写得非常专业,你明显的感觉到自己有了成长和收获满满,然后你心满意足的下班了,约上前台小姐姐一起去吃晚饭了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2193069.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

个人网站,怎么操作才能提升个人网站的流量

运营个人网站以提升流量是一个综合性的过程,涉及内容优化、技术调整、用户体验提升以及外部推广等多个方面。以下是一些专业建议,旨在帮助个人网站运营者有效提升网站流量: 1.精准关键词研究与优化 -关键词研究:利用工具如谷歌…

关于 Python 3.13 你所需要知道的几点

什么是全局解释器锁 (GIL)? 自20世纪80年代末,Guido Van Rossum在荷兰阿姆斯特丹东部的一个科技园区开始开发Python编程语言,它最初被设计为一种单线程的解释型语言。这到底是什么意思呢? 你可能会听说,编程语言分为解…

C++-容器适配器- stack、queue、priority_queue和仿函数

目录 1.什么是适配器 2.deque 1.简单了解结构 2.deque的缺陷 3.为什么选择deque作为stack和queue的底层默认容器 3.stack(栈) 4.queue(队列) 5.仿函数 6.priority_queue(优先级队列)(堆…

IDEA 2024.3 预览:把开发者感动到哭了

幸运的人, 一生都被童年治愈; 不幸的人, 一生都在治愈童年 只有勇敢的人 和有钱的人才能先享受世界 缘分就是我还不知道 会见到你就误打误撞般 遇见了你 最近 IDEA 又发布了最新的 2024.3 的预览版本 EAP,把开发者的心激动的…

躺平成长-第四周的开发日记

回顾自己的小程序,现在自己有饮食/跑步/养生操/学习/学历提升/等多样化的kp值计算公式页面,自己tarbar导航页面有两个内容,kp值计算,躺平显示,单纯这些功能自己使用下来,会感到一种疲惫!&#x…

详解Java中的堆内存

详解Java中的堆内存 堆是JVM运行数据区中的一块内存空间,它是线程共享的一块区域(注意了!!!),主要用来保存数组和对象实例等(其实对象有时候是不在堆中进行分配的,想要了…

霓虹灯数字时钟(可复制源代码)

文章目录 一、效果演示二、CodeHTMLCSSJavaScript 三、实现思路拆分CSS 部分JavaScript 部分 四、源代码 一、效果演示 文末可一键复制完整代码 二、Code HTML <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><…

我对软件工程的理解

1 引言 从事软件行业这么年&#xff0c;写了10年代码&#xff0c;又从事了多年的项目产品方面的工作&#xff0c;一些每天用到的软件工程的方法&#xff0c;虽然天天都在用但一些概念总感觉似是而非&#xff0c;正好借假期的时间&#xff0c;好好整理下&#xff0c;以供自己或…

AI配音(声音克隆)

Fish Audio: Free Generative AI Text To Speech & Voice Cloning 【【AI配音】终于找到免费 & 小白友好的声音克隆软件了&#xff01;真人相似度98%!】https://www.bilibili.com/video/BV1MwbFeCE2X?vd_source3cc3c07b09206097d0d8b0aefdf07958 我终于找到总这3款免…

Java中的封装、继承、多态

目录 封装 概念 包 继承 多态 向上转型 一、直接赋值 二、方法传参 三、返回值 向上转型注意事项 向下转型 格式 重写 重写和重载的区别 动态绑定 静态绑定和动态绑定 封装 概念 简单来说就是套壳屏蔽细节。 举例&#xff1a; 想要访问它们时需要一些“接口”…

Codeforces Rund 977 div2 个人题解(A~E1)

Codeforces Rund 977 div2 个人题解(A,B,C1,C2,E1) Dashboard - Codeforces Round 977 (Div. 2, based on COMPFEST 16 - Final Round) - Codeforces 火车头 #define _CRT_SECURE_NO_WARNINGS 1#include <algorithm> #include <array> #include <bitset> …

Java之二叉树的基本操作实现

1. 模拟实现二叉树前&#xff0c;我们要先表示树&#xff0c;首先定义一个内部类&#xff0c;当作二叉树节点 static class TreeNOde{char val;//存放二叉树的值TreeNOde left;//指向左子树的引用TreeNOde right;//指向右子树的引用//构造方法&#xff0c;用于实例化树的节点p…

信息学奥赛复赛复习13-CSP-J2021-02插入排序-排序稳定性、插入排序、sort排序、结构体、计数排序

PDF文档回复:20241006 1P7910 [CSP-J 2021] 插入排序 [题目描述] 插入排序是一种非常常见且简单的排序算法。小 Z 是一名大一的新生&#xff0c;今天 H 老师刚刚在上课的时候讲了插入排序算法。 假设比较两个元素的时间为 O(1)&#xff0c;则插入排序可以以 O(n^2) 的时间复…

第五节——转移表(让你不再害怕指针)

文章目录 制作简易计算器什么是转移表&#xff1f;switch函数实现函数指针数组实现 制作简易计算器 要求&#xff1a;制作一个简易计算器&#xff0c;可以进行* / - 等功能运算。 什么是转移表&#xff1f; 指的就是通过函数指针数组的方式通过数组去调用里面的函数&#x…

LeetCode讲解篇之239. 滑动窗口最大值

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们维护一个长度为k的窗口&#xff0c;然后窗口从数组最左边一直移动到最右边&#xff0c;记录过程中窗口中的最大值&#xff0c;就是答案 我们每次查询长度为k的窗口最大值是什么时间复杂度是O(k)的&#xff0…

软件验证与确认实验二-单元测试

目录 1. 实验目的及要求.................................................................................................... 3 2. 实验软硬件环境.................................................................................................... 3 …

idea插件市场安装没反应

https://plugins.jetbrains.com/idea重启后还是不行那就

163页PPT罗兰贝格品牌战略升级:华为案例启示与电器集团转型之路

罗兰贝格作为一家全球顶级的战略管理咨询公司&#xff0c;其品牌战略升级理念在多个行业中得到了广泛应用。以下将以华为案例为启示&#xff0c;探讨电器集团的转型之路&#xff0c;并融入罗兰贝格品牌战略升级的思想。 一、华为案例的启示 华为与罗兰贝格联合撰写的《数据存…

基于java+springboot的酒店预定网站、酒店客房管理系统

该系统是基于Java的酒店客房预订系统设计与实现。是给师弟开发的毕业设计。现将源代码开放出来&#xff0c;感兴趣的同学可以下载。 演示地址 前台地址&#xff1a; http://hotel.gitapp.cn 后台地址&#xff1a; http://hotel.gitapp.cn/admin 后台管理帐号&#xff1a; 用…

基于阻塞队列及环形队列的生产消费模型

目录 条件变量函数 等待条件满足 阻塞队列 升级版 信号量 POSIX信号量 环形队列 条件变量函数 等待条件满足 int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 参数&#xff1a; cond&#xff1a;要在这个条件变量上等待 mutex…