python详细安装教程3.8.5(带你玩转 python 中的装饰器)python教程 / python装饰器与生成器教程...

wufei123 发布于 2024-06-25 阅读(1)

前言当你使用 python 久了之后,会发现它是以一门非常灵活的语言,说到这,不得不谈到 python 里面的装饰器,这玩意真的很好用比如著名的 web 框架 flask 支持使用装饰器来将函数与网址绑定。

这极大地方便了我们进行网站路径访问管理 ​同时,如果你想让某个功能只能给 VIP 使用,那可以自己构造一个装饰器,在执行要调用的函数之前,先执行装饰器里面的验证流程, ​验证通过了再同意调用被装饰的函数(即需要 VIP 才能调用的函数)。

在写好装饰器之后,只需要在被调用函数上面加一行代码(形式为::@function_name)便可以很好地管理访问权限(试想假设有好几十个函数需要 VIP,如果你在这种函数里面写验证,那很容易会遗漏某个函数,而且想让该函数不需要 VIP 也能被执行掉执行的话,修改代码也容易出错)。

​在这篇文章中,我将先展示两个装饰器的实用例子实现一个计算函数执行耗时的装饰器实现一个可以在函数执行前后,输出执行信息的装饰器(执行信息作为装饰器的参数)接着,我会手把手教你实现一个支持函数执行出错重试 n 次的装饰器

如果反馈够好,我会加一个重磅实例:手写一个多线程函数装饰器,极大简化多线程操作代码 ​开始之前Python版本要求Python 3如果没有安装 Python,可以参考我写的这篇安装教程Win 10 系统下搭建 Python 编程环境。

80 赞同 · 22 评论文章

需要安装的库(这个库用来输出东西,显示效果好看!!!)rich库的安装方法是:打开 cmd(命令提示符或者其他终端工具),输入以下代码pip install rich输入完毕,按 Enter 键执行代码,等待 successfully 出现即可。

正文装饰器实例展示不携带额外参数的装饰器:函数执行耗时计时器importrichimporttimefromdatetimeimportdatetimedeftimeit(func):""" 1. @ 后面的函数 timeit 以 func 作为参数

2. timeit 经过 调用之后返回函数 decorator 3. decorator 接受 *args, **kwargs 这两个参数 4. 其中 args 实际上是 tuple 类型,kwargs 是 dict 类型

5. 调用函数的必要条件是:函数名称、调用函数需要的参数 6. 因为 调用 timeit 之后,得到 decorator 7. 这个时候 decorator 相当于被传进来的函数 func 的名称

8. 在函数里 decorator 通过 func(*args,**kwargs) 调用函数 func 9. 在函数 decorator 里返回 func(*args,**kwargs) 的执行结果

注意: func(*args,**kwargs) 即可调用被传进来的函数(无论原来的函数参数有多少个以及形式怎么样) 执行路线: (function) timeit: (func) -> (*args, **kwargs) -> Any

Parameters ---------- func : object 要被装饰的函数 Examples -------- >>> import time

>>> @timeit ... def sleep(seconds: int) -> None: ... time.sleep(seconds) >>> sleep(2)

function "sleep" started at 2021-08-18 20:29:12.263678 function "sleep" finished at 2021-08-18 20:29:14.401920

cost time: 2.13824200630188 s """defdecorator(*args,**kwargs):# 获取函数执行开始前的时间s=datetime.today()

# 输出一些信息rich.print(ffunction "{func.__name__}" started at {s} )# 开始执行函数value=func(*args,**kwargs)# 获取函数执行开始后的时间

e=datetime.today()# 计算执行耗时cost=e.timestamp()-s.timestamp()# 输出一些信息rich.print(ffunction "{func.__name__}" finished at {e}

)# 输出耗时秒数rich.print(fcost time: {cost} s)returnvaluereturndecorator@timeitdefsleep(seconds:int)->None

:""" 暂停指定时间 Parameters ---------- seconds : int 暂停的秒数 """time.sleep(seconds

)if__name__==__main__:# 调用函数sleep(2)执行结果输出function "sleep" started at 2021-08-18 21:36:21.940456 function "sleep" finished at 2021-08-18 21:36:24.062276 cost time: 2.1218199729919434 s

执行过程截图

关键代码注释图片

携带额外参数的装饰器:函数计时器+在函数执行前后打印指定信息importrichimporttimefromdatetimeimportdatetimedeflog_at_start_and_end(start_msg

:str=start,end_msg:str=end):""" 1. @ 后面的函数 log_at_start_and_end 在传入参数 start_msg 和 end_msg 之后被调用 2. 函数 log_at_start_and_end 被调用之后返回函数 decorator

3. 被装饰的函数 func 被作为参数传入函数 decorator 4. 函数 decorator 被调用之后返回 函数 wrapper 5. 调用函数 wrapper 需要 *args,**kwargs

6. 此时函数 wrapper 相当于被装饰的函数 func 7. 在函数 wrapper 中通过 func(*args,**kwargs) 来调用函数 func 8. 最后在函数 wrapper 中返回 func(*args,**kwargs) 的执行结果

注意: func(*args,**kwargs) 即可调用被传进来的函数(无论原来的函数参数有多少个以及形式怎么样) 执行路线: (function) log_at_start_and_end: (start_msg: str = start, end_msg: str = end) -> (func) -> (*args, **kwargs) -> Any

Parameters ---------- start_msg : str, optional 执行被装饰器的函数前要输出的信息, by default start

end_msg : str, optional 执行被装饰器的函数后要输出的信息, by default end Examples -------- >>> import time

>>> @log_at_start_and_end(start_msg=开始了,忍一下!,end_msg=结束了,感觉如何?) ... def sleep(seconds: int) -> None:

... time.sleep(seconds) >>> sleep(2) 开始了,忍一下! function "sleep" started at 2021-08-18 21:07:27.129993

function "sleep" finished at 2021-08-18 21:07:29.249629 cost time: 2.119636058807373 s 结束了,感觉如何?

"""defdecorator(func):defwrapper(*args,**kwargs):# 获取函数执行开始前的时间s=datetime.today()rich.print(start_msg

)# 输出一些信息rich.print(ffunction "{func.__name__}" started at {s} )# 开始执行函数value=func(*args,**kwargs)# 获取函数执行开始后的时间

e=datetime.today()# 计算执行耗时cost=e.timestamp()-s.timestamp()# 输出一些信息rich.print(ffunction "{func.__name__}" finished at {e}

)# 输出耗时秒数rich.print(fcost time: {cost} s)rich.print(end_msg)returnvaluereturnwrapperreturndecorator@log_at_start_and_end

(start_msg=开始了,忍一下!,end_msg=结束了,感觉如何?)defsleep(seconds:int)->None:""" 暂停指定时间 Parameters ----------

seconds : int 暂停的秒数 """time.sleep(seconds)if__name__==__main__:# 调用函数sleep(2)执行结果输出开始了,忍一下! function "sleep" started at 2021-08-18 21:34:16.455638 function "sleep" finished at 2021-08-18 21:34:18.580148 cost time: 2.1245100498199463 s 结束了,感觉如何?

执行过程截图

关键代码注释图片

一步步实现简单、复杂的装饰器函数的定义以及调用这是一个非常重要的知识因为,你要搞一个装饰器,得考虑到调用被装饰的函数所需的参数个数以及形式的不确定的如果你的装饰器对被装饰的函数有参数个数或者形式限制,那么这个装饰器通用性就会非常差。

​比如说你要搞一个装饰器,让被装饰的函数在执行出错时重试 n 次,这种装饰器就应该是一个通用的装饰器,不管什么函数都应可以被其成功装饰并且在形式上能够被正常调用所以显然得考虑怎么样才能适配被装饰的函数。

​假设下面有这么一个函数defsay(msg:str)->None:print(msg)print(type:,type(msg))say(Hello World!)代码运行输出Hello World! type:

下面再来看一个defsay_plus(*msg)->None:print(msg)print(type:,type(msg))say_plus(Hello World!,Welcome)代码运行输出(Hello World!, Welcome) type:

不考虑函数的名称,对比两段代码,就函数参数定义层面而言,两者的区别在于,前一个的参数前面没有 *,而后面的参数里面有 ​第一端代码的函数 say 调用方式很容易理解,毕竟函数定义了几个参数就要填几个可是后面的的函数 。

say_plus 就奇怪了从形式上,它的定义里面确实是只有一个参数的但是在调用的时候竟然可以通过 say_plus(Hello World!, Welcome) 传递两个参数来调用! ​仔细回顾前面说到的两个函数的不同点。

你可能会想到,是不是因为 参数 msg 前面有个 *,所以函数 say_plus会更加“强大”呢? ​确实如此在 python 里面,如果你定义的函数的参数前面有个 *,那么你将被允许在该填这个参数的位置,填写任意多个参数以及任意类型的参数(参数之间用 。

, 分隔) ​观察后一段代码的输出之后,我们不难发现,msg 的类型变成了 tuple这证明,当一个参数前面有 * 时,如果你在调用该函数时,在该填这个参数的位置填的任意多个(可以不填,可以填很多个)参数,那么带 。

* 的这个函数形参将会被解释为包含你所填的任意多个参数的 tuple这样子便可以达到不限制参数个数的目的! ​再来看一段代码 defsay_plus_plus(*args,**kwargs)->None。

:print(args)print(type:,type(args))print(---------------------)print(kwargs)print(type:,type(kwargs))

say_plus_plus(Hello World!,Welcome,name=John,age=18)代码运行输出(Hello World!, Welcome) type: --------------------- {name: John, age: 18} type:

这个函数真的牛逼! 你在调用的时候,几乎没有限制,你可以不填参数,可以填任意多个参数,还可以填 key=value 这种东西 这意味这,如果你可以拿到一个函数的 *args以及 **kwargs 还有名称,那么你就可以任意调用这个函数了!不会受到参数形式以及个数的限制。

这不满足了前面说的:装饰器就应该是一个通用的装饰器,不管什么函数都应可以被其成功装饰并且在形式上能够被正常调用 吗? ​最后贴一个非常实用的代码片段defreal_func(*args,**kwargs

):print(args,args)print(kwargs,kwargs)print(-------------)deffunc(*args,**kwargs):# 调用函数 real_funcreal_func

(*args,**kwargs)# 无参数调用func()# 有一个参数调用func(1)# 以 key=value 的形式调用func(a=2)# 前两者的组合func(1,2,a=3)代码运行输出args () kwargs {} ------------- args (1,) kwargs {} ------------- args () kwargs {a: 2} ------------- args (1, 2) kwargs {a: 3} -------------

函数的参数在 python 里面,函数的参数类型是不被限制的,这意味着你可以以任意类型的数据作为调用函数时用的参数那么是不是可以把函数作为调用某个函数的参数呢?答案是肯定的!函数的返回值在 python 里面,你可以在一个函数里面返回任意类型的东西,以及任意个数的东西。

那么这个时候就会有人开始奇思妙想了,比如以实现一个函数执行计时器为例 ​想要计算函数的执行耗时,那么就必须获取到函数执行前后的时刻然后两者作差,最终求出函数执行耗时而获取这两个时刻的前提是,你拥有调用该函数的权限以及能正确地调用。

​前面有说到,调用函数有两个必要条件:正确的函数名称以及正确的参数(形式和数量上) 重点来了,如果我设计一个函数 A ,其可以接受其他函数(假设叫函数 B 吧)作为参数,然后我再在函数 A 里面定义一个形式为。

deffunc(*args,**kwargs):...的函数 func,接着,我在函数 A 里面返回函数 func ,那么调用函数 B 时,B 的参数便会被传入func,而 func刚好是前面说的那种非常强大的函数(不受参数形式以及个数限制)。

这个时候,我们便拥有了函数 B 的名称以及调用它所需要的参数那岂不是就可以调用它了?如此以来,便可以 在 func 里面,在调用 函数 B 的前后插入代码了! ​纯文字解释或许难以理解,所以我们来看一下代码吧!

importtimedefA(B):deffunc(*args,**kwargs):print(f开始调用函数 {B.__name__})# 开始调用被传入的函数value=B(*args,**kwargs

)print(f完成调用函数 {B.__name__})# 返回函数调用结果returnvalue# 返回函数 func 以与被装饰的函数共享参数returnfunc@AdefB(seconds:int

):time.sleep(seconds)B(1)运行输出开始调用函数B完成调用函数B以上代码是对前面原理解释的另一种表现形式 有了这个基础,再回头看前面实例里面的函数执行耗时计时器代码,是不是更容易理解了呢? ​。

这里再贴一遍之前的代码吧(记得安装必要的库 rich)importrichimporttimefromdatetimeimportdatetimedeftimeit(func):""" 1. @ 后面的函数 timeit 以 func 作为参数

2. timeit 经过 调用之后返回函数 decorator 3. decorator 接受 *args, **kwargs 这两个参数 4. 其中 args 实际上是 tuple 类型,kwargs 是 dict 类型

5. 调用函数的必要条件是:函数名称、调用函数需要的参数 6. 因为 调用 timeit 之后,得到 decorator 7. 这个时候 decorator 相当于被传进来的函数 func 的名称

8. 在函数里 decorator 通过 func(*args,**kwargs) 调用函数 func 9. 在函数 decorator 里返回 func(*args,**kwargs) 的执行结果

注意: func(*args,**kwargs) 即可调用被传进来的函数(无论原来的函数参数有多少个以及形式怎么样) 执行路线: (function) timeit: (func) -> (*args, **kwargs) -> Any

Parameters ---------- func : object 要被装饰的函数 Examples -------- >>> import time

>>> @timeit ... def sleep(seconds: int) -> None: ... time.sleep(seconds) >>> sleep(2)

function "sleep" started at 2021-08-18 20:29:12.263678 function "sleep" finished at 2021-08-18 20:29:14.401920

cost time: 2.13824200630188 s """defdecorator(*args,**kwargs):# 获取函数执行开始前的时间s=datetime.today()

# 输出一些信息rich.print(ffunction "{func.__name__}" started at {s} )# 开始执行函数value=func(*args,**kwargs)# 获取函数执行开始后的时间

e=datetime.today()# 计算执行耗时cost=e.timestamp()-s.timestamp()# 输出一些信息rich.print(ffunction "{func.__name__}" finished at {e}

)# 输出耗时秒数rich.print(fcost time: {cost} s)returnvaluereturndecorator@timeitdefsleep(seconds:int)->None

:""" 暂停指定时间 Parameters ---------- seconds : int 暂停的秒数 """time.sleep(seconds

)if__name__==__main__:# 调用函数sleep(2)前面我们成功地实现了一个可以计算函数执行耗时的装饰器但是这样子的装饰器有个不足之处,那就是无法额外提供一些参数来控制装饰器的表现 ​。

最前面有提到,著名框架 flask 通过装饰器很好地完成函数与网址之间的绑定其中的一个原因是 flask 提供的装饰器是可以额外提供参数的例如下面的这个 flask 的hello world 代码from

flaskimportFlaskapp=Flask(__name__)@app.route("/")defhello_world():return"

Hello, World!

"可以看到 @app.route("/")

这么一个表达方式这与我们实现的装饰器不同点在于,它的装饰器的装饰函数是属于某个 class的,比如这里的 app 是 class ``Flask 的一个实例,而 route 则是这个实例里面提供的一个 。

function(好比人类是一个抽象的 class``Human,而每一个人都是人类的实例,而你具有的诸如走路、使用工具等技能相当于 function,有些技能的使用是需要一定的条件的,比如取快递需要提供姓名,这些叫参数 ) ​。

那么怎么样才能实现上面的带参数的装饰器呢?分析过程至少得定义一个被装饰的函数 S至少得定义一个函数 A ,这个函数可以可以放置一些参数,例如 Class FLask 中的 route(/)至少得定义一个函数 B,函数 B 仅以函数 S 为参数

至少得定义一个万能函数 C(*args,**kwargs),并且其可以接受任意多个以及任意形式的参数有了以上步骤,我们开拼接成一个完整的调用路径吧调用 函数 A函数 A 返回函数 B 函数 B 返回一个形式上”万能“的函数。

C(*args,**kwargs)函数 C 从函数 B 里面读取到被传进来的函数 S在函数 C 里面通过 S(*args,**kwargs) 调用函数 S,最后返回调用结果具体代码如下defA(name

=John):defB(S):defC(*args,**kwargs):print(fWelcome {name}!)value=S(*args,**kwargs)print(fGoodbye {name}!

)returnvaluereturnCreturnB@A(name=Mike)defS(msg:str)->None:print(msg)S(Here)代码运行输出Welcome Mike! Here Goodbye Mike!

有了此基础,再回顾前面的带有参数的装饰器实例,我们来写一个支持函数执行出错重试 n 次的装饰器吧!实操之实现一个 支持函数执行出错重试 n 次的装饰器importrandomimportrichdefretry

(tries:int=3,success_msg:str=执行成功,failure_msg:str=执行失败):""" 可以让函数重试指定次数的装饰器 Parameters ----------

tries : int, optional 最大重试次数, by default 3 success_msg : str, optional 执行成功时输出的信息, by default 执行成功

failure_msg : str, optional 执行失败时输出的信息, by default 执行失败 """defdecorator(func):defwrapper

(*args,**kwargs):foriinrange(tries):try:# 调用被装饰的函数value=func(*args,**kwargs)rich.print(success_msg,尝试次数:

,i+1)returnvalueexcept:# 达到最大重试次数,报错ifi==tries-1:rich.print(failure_msg,最大尝试次数:,tries)raiseException# 返回形式上万能的函数,以与被装饰的函数共享参数

returnwrapper# 返回接受函数作为参数的函数returndecorator@retry(tries=5)defget_random_number():""" 产生指定范围的随机整数,不等于指定值就报错

Raises ------ Exception 报错信息 """number=random.randint(1,10)ifnumber!=1:raiseException

(出错了, 随机数未等于 1)# 测试get_random_number()执行以上代码,你会发现有成功执行的时候,也有不成功执行的时候 例如下面的两个输出执行成功 尝试次数: 3 执行失败 最大尝试次数: 5 Traceback (most recent call last): File "hello.py", line 26, in wrapper value = func(*args, **kwargs) File "hello.py", line 52, in get_random_number raise Exception(出错了, 随机数未等于 1) Exception: 出错了, 随机数未等于 1 During handling of the above exception, another exception occurred: Traceback (most recent call last): File "hello.py", line 55, in get_random_number() File "hello.py", line 33, in wrapper raise Exception Exception。

写在最后本文从实用性的角度出发,引导大家”造轮子“,希望大家在阅读之后可以实操一下,做出可以极大提高自己工作效率的装饰器 ​装饰器与我而言真的非常有用,比如我开发的 python 库 efinance 其中的一段代码

直接戴了两顶帽子。部分代码甚至可以戴四顶帽子。 对其感兴趣的可以上 Github 看看,这是项目链接 ​

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

河南中青旅行社综合资讯 奇遇综合资讯 盛世蓟州综合资讯 综合资讯 游戏百科综合资讯 新闻48659