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

[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"] = Trueelse: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"] = Truefunc()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+1plus2 = lambda x:x+1

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

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

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

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

def plus(n):return n+1print(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"] = Truefunc()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"] = Truefunc()else:print("wrong username or password!")else:print("用户已登录,验证通过...")return innerdef 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"] = Truefunc()else:print("wrong username or password!")else:print("用户已登录,验证通过...")return innerdef 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"] = Truefunc(*args, **kwargs)else:print("wrong username or password!")else:print("用户已登录,验证通过...")return innerdef 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"] = Truefunc(*args,**kwargs) # 认证成功,执行功能函数,使用到外部函数的func参数else:print("wrong username or password!")else:print("用户已登录,验证通过...")func(*args,**kwargs)  # 认证成功,执行功能函数return innerdef 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"] = Trueaccount["is_paid"] = Truefunc(*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"] = Truefunc(*args,**kwargs)else:print("wrong password!")else:print("user is online and paid.")func(*args, **kwargs)return innerdef 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.rhkb.cn/news/440492.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis:list类型

Redis:list类型 list命令非阻塞LPUSHLRANGELPUSHXRPUSHRPUSHXLPOPRPOPLINDEXLINSERTLLENLREMLTRIMLSET 阻塞BLPOPBRPOP 内部编码ziplistlinkedlistquicklist 几乎每种语言都有顺序表、数组、链表这样的顺序结构,Redis也做出了相应的支持。 如图&#xff…

案例-博客页面简单实现

文章目录 本文内容只涉及前端1. 内容要求2. 画面展示初始化面演示视频 3. 注意事项4. 代码区js文件夹下的jquery.min.js内容登录代码列表页面创作页面 本文内容只涉及前端 1. 内容要求 登录页面实现博客列表页面实现博客创作页面实现 链接: 开源在线 Markdown 编辑器文本框可…

MATLAB智能优化算法-学习笔记(4)——灰狼优化算法求解旅行商问题【过程+代码】

灰狼优化算法(Grey Wolf Optimizer, GWO)是一种基于灰狼社会行为的元启发式算法,主要模拟灰狼群体的捕猎行为(包括围攻、追捕、搜寻猎物等过程)。多旅行商问题(Multi-Traveling Salesman Problem, mTSP)是旅行商问题(TSP)的扩展,它涉及多个旅行商(车辆)从一个起点城…

免杀对抗—javaASMMSF源码特征修改汇编调用CS内联C

前言 今天讲最后的两个语言java和汇编,那么基本所有语言就讲了一个遍了。java在后门免杀这一块呢其实是有点鸡肋的,其它语言编译成的是exe,而java编译成的是jar包,而jar包又得有java环境才能运行,不像exe是个电脑都行…

C++ : STL容器之string剖析

STL容器之string剖析 一、string 的迭代器(一)起始迭代器(二)末尾迭代器(三)反向迭代器 二、容量相关的函数(一)size(二)capacity(三)…

【java】数据类型与变量以及操作符

各位看官:如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论,感谢您的支持!!! 目录 一.字面变量: 二:数据类型 1.1:int类型:&#xff0…

无人机(自组穿越机,航模)-芯片选型

飞控MCU: 型号尺寸子型号参数规格备注STM325*532位ARM Cortex-M3 CPU,72MHz,256KB Flash,20KB RAMLQFP 48F33*332位ARM Cortex-M4 CPU,72MHz,256KB Flash,40KB RAMMPU6050F45*532位ARM Cortex-M4 CPU&…

github学生认证(Github Copilot)

今天想配置一下Github Copilot,认证学生可以免费使用一年,认证过程中因为各种原因折腾了好久,记录一下解决方法供大家参考。 p.s.本文章只针对Github学生认证部分遇到的问题及解决方法,不包括配置copilot的全部流程~ 1、准备工作…

如何使用ssm实现基于Java的校园二手物品交易平台的设计与实现+vue

TOC ssm789基于Java的校园二手物品交易平台的设计与实现vue 绪论 1.1 研究背景 在这个推荐个性化的时代,采用新技术开发一个校园二手物品交易平台来分享和展示内容是一个永恒不变的需求。本次设计的校园二手物品交易平台有管理员,商家,用…

Git大框架总结

下面首先是我对于git的一个小总结,主要是大框架 首先是四区,因为大部分你所有的工作都是在这四个区里的实现的,包括要提交一个东西,是先是在工作区修改,后用add添加到暂存区,后提交到本地仓库,当…

系统架构设计师论文《论企业应用系统的分层架构风格》精选试读

论文真题 软件架构风格是描述一类特定应用领域中系统组织方式的惯用模式,反映了领域中诸多系统所共有的结构特征和语义特征,并指导如何将各个模块和子系统有效组织成一个完整的系统。分层架构是一种常见的软件架构风格,能够有效简化设计&…

基于WxJava框架的集客微信公众号的设计与实现(项目运行说明)

项目运行说明 数据库 系统采用MySQL数据库和Redis数据库,读者可参考在码云项目(code/yok/src/main/resources)中的application.yml中自行配置MySQL数据库,在redis.properties中配置Redis。 数据库表的创建语句在yok项目中的create_dataBase.sql文件中。 项目启动 后端项目…

JAVA思维提升

利用java做一个双色球彩票系统 要求 package ZY; import java.util.Random; import java.util.Scanner; public class Test9双色球 { //目标:模拟双色球//规则投注号码由6个红色球号码和1个蓝色球号码组成。红色球号码从1-33中选择;蓝色球号码从1-16中选择。publi…

ElasticSearch备考 -- Alias

一、题目 1) Create the alias hamlet that maps both hamlet-1 and hamlet-2 Verify that the documents grouped by hamlet are 8 2) Configure hamlet-3 to be the write index of the hamlet alias 二、思考 可以通过指定别名,来指向一个或多个索引&#xff0c…

Java环境配置

下载安装JDK 选择长期稳定的版本jdk-21 安装 安装好之后查看bin目录,里面存放了各种工具命令,有比较重要的javac和java。 javac.exe 是 Java 编译器,用于将 Java 源代码(.java 文件)编译成字节码(.class…

白嫖EarMaster Pro 7简体中文破解版下载永久激活

EarMaster Pro 7 简体中文破解版功能介绍 俗话说得好,想要成为音乐家,就必须先拥有音乐家的耳朵,相信很多小伙伴都已经具备了一定的音乐素养,或者是说想要进一步得到提升。那我们就必须练好听耳的能力,并且把这种能力…

[C语言]指针和数组

目录 1.数组的地址 2.通过指针访问数组 3.数组和指针的不同点 4.指针数组 1.数组的地址 数组的地址是什么&#xff1f; 看下面一组代码 #include <stdio.h> int main() { int arr[5] {5,4,3,2,1}; printf("&arr[0] %p\n", &arr[0]); printf(&qu…

使用C语言进行图形化编程:从入门到实践的全面指南

1. 引言 随着技术的进步和个人电脑性能的提升&#xff0c;图形用户界面&#xff08;Graphical User Interface, GUI&#xff09;已经成为软件开发的重要组成部分。尽管C语言本身并不直接支持GUI编程&#xff0c;但借助各种库和框架&#xff0c;C语言也能成为创建功能强大且美观…

嵌入式硬件设计

嵌入式硬件设计是指针对嵌入式系统&#xff08;一种专用的计算机系统&#xff0c;通常嵌入到其他设备中&#xff09;进行的硬件设计工作。嵌入式系统广泛应用于消费电子、工业控制、医疗设备、汽车电子、航空航天等领域。以下是嵌入式硬件设计的主要内容和步骤&#xff1a; 1.…

【unity游戏开发】彻底理解AnimatorStateInfo,获取真实动画长度

前言 前置知识&#xff1a;设置参数后&#xff0c;下一个循环才会切换对应动画&#xff0c;所以在下一个循环获取真实的动画长度 AnimatorStateInfo是结构体&#xff01;值类型&#xff0c;要不断重复获取才是最新的 主要是自动设置trigger切换的动画自动切回上一个动画&#x…