来源:早起Python
作者:刘早起
大家好,我是早起。
之前的文章中提到,很多人认为理解了装饰器的概念和用法后,会觉得自己的 Python 水平有一个明显的提高。
但很多教程在一上来就会给出装饰器的定义以及基本用法,例如你一定会在很多文章中看到例如代码运行时间计时器等相关常用装饰器。
直接从应用入手这样学习当然十分有效,但不是看过就忘就是似懂非懂的状态,因为装饰器从来就不是一个单独的概念,就像数学分析中求积分一样,你可以通过公式快速算出需要求的积分,但是若明白积分是由极限定义的,之后再看积分将会是不一样的视角。
本文我将尝试说清楚为什么需要现装饰器、什么是装饰器、以及如何写一个简单的装饰器,但要彻底理解装饰器还要从函数开始说起,下面是有关函数的四个重要的概念,希望大家可以明白。
相信你在大多数文章中,至少也能知道例如「装饰器是装饰函数」,「在不修改函数代码的情况下增加额外功能」等核心概念,但首先要知道为什么函数能够被装饰。
例如在《流畅的Python》一书中,讲到函数的一开始就提出了一个概念,函数是一等对象
正如书中所说,在Python中一个函数既可以作为参数被传递,也能作为另一个函数的返回值,这也是函数可以被装饰的关键,在介绍装饰器之前,下面有必要通过简单的代码对这段话做一个更直观的理解。
1.1 函数中传递函数
函数中传递函数意思就是可以将函数当作变量来使用,我们来看一个简单的示例。
在下面的代码中,func1是一个普通的函数,接受两个参数a,b并返回他们的和。func2不一样的地方在于多接收了一个func参数,这个func变量需要是一个函数
def func1(a,b): print(f"函数 {func1.__name__} 正在执行") return a + b def func2(func,c,d): print(f"函数 {func2.__name__} 正在执行") return func(c,d)
现在让我们来执行func1
>>> func1(1,2)
函数 func1 正在执行 3
下面func1作为参数执行func2
>>> func2(func1,3,4)
函数 func2 正在执行
函数 func1 正在执行 7
可以看到,先执行func2,在func2接收到fun1后,再次执行func1并返回。注意这里的func1没有括号,它只不过是和a,b一样的参数被使用,理解这点后我们继续看下一个知识点。
1.2 函数中定义函数
在定义一个函数后,可以继续在函数内部定义新的函数。为了理解这点,我们来看下面简单的示例。
我们先定义了一个函数func1,并在func1中定义了func2,并在func1的内部调用了func2
def func1(): print(f"函数 {func1.__name__} 正在执行") def func2(): print(f"内部函数 {func2.__name__} 正在执行")
func2()
现在执行func1与func2看看会发生什么
>>> func1()
函数 func1 正在执行
内部函数 func2 正在执行 >>> func2()
------------------------------------------------
Traceback (most recent call last)
----> 1 func2() NameError: name 'func2' is not defined
可以看到,当执行func1时,会自动执行func2,但是如果单独执行func2,则提示未定义,说明func2只能在func1中被调用!
1.3 函数返回函数
最后是一个函数可以将另一个函数作为返回值返回的简单示例,在下面的代码中,我们先定义了一个外部函数func1(接受一个参数a),之后定义了一个内部函数func2(接受一个参数b)并返回a + b,最后将func2作为func1的返回值返回
def func1(a): print(f"函数 {func1.__name__} 正在执行") def func2(b): print(f"函数 {func2.__name__} 正在执行") return a + b return func2
需要注意的是,这里返回的func2没有括号,代表返回的是func2的地址!
>>> func3 = func1(1) >>> func3
函数 func1 正在执行
<function __main__.func1.<locals>.func2(b)> >>> func3(2)
函数 func2 正在执行 3
从上面的运行结果可以看到,当执行func1(1)后,返回的是func2的地址,并赋给func3,之后执行func3(2)才真正执行了内部函数func2!
现在我们就解决了上一小节的问题「将内部函数func2单独拿出来用」!
1.4 函数内省
函数内省是相对来说比较好理解的一个概念,在Python中的意思就是我们可以访问函数的部分属性,例如print函数,可以使用dir函数来查看其全部属性
>>> dir(print)
['__call__', '__class__', '__delattr__',
··· ··· '__subclasshook__', '__text_signature__']
现在可以查看其对应的属性
>>> print.__name__ 'print' >>> print.__call__
<method-wrapper '__call__' of builtin_function_or_method object at 0x7fddb8056b80> >>> print.__doc__ "print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)nnPrints the values to a stream, or to sys.stdout by default.nOptional keyword arguments:nfile: a file-like object (stream); defaults to the current sys.stdout.nsep: string inserted between values, default a space.nend: string appended after the last value, default a newline.nflush: whether to forcibly flush the stream."
函数内省了解到这个程度即可,我们会在2.3节再次提到这里的知识!
至此,我已经将接触装饰器之前必须要吃透的知识点介绍完毕,如果你觉得我讲解的不够清晰,可以查看任何其他教程或者书籍弄懂后再继续阅读。
现在终于可以来说说装饰器了,当然绝对不是直接告诉你一个写好的装饰器,而是我们一点一点去写一个简单的装饰器。
2.1 第一个装饰器
在下面的代码中,我们先定义了一个函数first_decorator,该函数接受函数为参数(如果不理解请查看本文 1.1 节),之后在内部定义了一个名为name_wrapper的内部函数(如果不理解请查看本文 1.2 节),最后返回以name_wrapper作为返回值(如果不理解请查看本文 1.3 节)
def first_decorator(func): def name_wrapper(): print(f"被装饰的函数 {func.__name__} 即将执行")
func()
print(f"被装饰的函数 {func.__name__} 执行完毕") return name_wrapper
这个函数的功能是,在执行被接收函数前后分别打印一段话,所以我们要再定义一个函数来测试效果
def add(): print("函数 add 正在执行 ")
这个fun1没有什么好说的,打印一段话。下面需要仔细看了,我们来执行这两段代码
>>> add = first_decorator(add)
>>> add()
被装饰的函数 add 即将执行
函数 add 正在执行
被装饰的函数 add 执行完毕
正如我们预料的一样,在执行add前后都有一段提示,但是如果每次使用first_decorator功能都需要先将add传递,之后再调用,来回写好几遍,实在太麻烦了!
因此这完全有更Pythonic的写法,也就是我们常见的装饰器形式,使用语法糖@,例如上面的例子和下面的写法等价
@first_decorator def add(): print("函数 add 正在执行 ")
用@+装饰器函数名字放在需要被装饰函数的上方即可,现在直接调用add即可实现装饰器的功能!
>>> add()
被装饰的函数 add 即将执行
函数 add 正在执行
被装饰的函数 add 执行完毕
相信看到这里,你应该明白装饰器@如何工作的,至少你在未来看到@时需要想到类似等价于add = first_decorator(add)一样的作用!
2.2 装饰器传参
上面仅是最简单的装饰器示例,在实际使用时
很自然的想法那就是加上参数呗,改起来也不难
@first_decorator def add(x,y): print("函数 add 正在执行 ")
print(f"{x} + {y} 的结果为{x+y}")
让我们来测试一下
>>> add(1,2)
-----------------------------------------------
Traceback (most recent call last)
<ipython-input-144-944f4051a32c> in <module> ----> 1 add(1,2) TypeError: name_wrapper() takes 0 positional arguments but 2 were given
不出意外的报错了,虽然我们给被装饰的函数加上了参数,但是在装饰器的内部函数name_wrapper()执行时并没有参数!
因此我们之前的代码可以这么改,使用*args, **kwargs也是非常常见的用法
def first_decorator(func): def name_wrapper(*args, **kwargs): print(f"被装饰的函数 {func.__name__} 即将执行")
func(*args, **kwargs)
print(f"被装饰的函数 {func.__name__} 执行完毕") return name_wrapper
现在我们再次使用这个装饰器即可返回我们预测的结果!
@first_decorator def add(x,y): print("函数 add 正在执行 ")
print(f"{x} + {y} 的结果为 {x+y}") >>> add(1,2)
被装饰的函数 add 即将执行
函数 add 正在执行 1 + 2 的结果为 3 被装饰的函数 add 执行完毕
本文的最后,还需要简单介绍一下在写装饰器时常用的functools模块。
还记得1.4节的函数内省相关知识吗?我们可以打印一个函数指向的内存地址或者名字等其他属性。
还是上面用到的add函数,我们都知道虽然被装饰了,但是功能上没有任何变化,依旧是计算两个数字的和,但是真的没有任何变化吗?下面让我们来观察一下
def add(x,y): print("函数 add 正在执行 ")
print(f"{x} + {y} 的结果为{x+y}") >>> print(add)
<function add at 0x7fddb9dd41f0>
>>>print(add.__name__)
add @first_decorator def add(x,y): print("函数 add 正在执行 ")
print(f"{x} + {y} 的结果为{x+y}") >>> print(add)
<function first_decorator.<locals>.name_wrapper at 0x7fddb9dd4e50> >>> print(add.__name__)
name_wrapper
可以看到,被装饰后,虽然功能上没有变化,但是它指向的是装饰器所定义的内部函数!这并不是我们希望看到的,比如若不同函数被两个装饰器装饰时则会出现一样的函数名!
幸运的是Python中的functools库可以轻松解决这个问题,只需要加上一行简单的代码就可以搞定!
import functools def first_decorator(func): @functools.wraps(func) def name_wrapper(*args, **kwargs): print(f"被装饰的函数 {func.__name__} 即将执行")
func(*args, **kwargs)
print(f"被装饰的函数 {func.__name__} 执行完毕") return name_wrapper @first_decorator def add(x,y): print("函数 add 正在执行 ")
print(f"{x} + {y} 的结果为{x+y}") >>> print(add)
<function add at 0x7fddb9dd4e50> >>> print(add.__name__)
add
可以看到此时函数名等函数属性均保留下来了,事实上@functools.wraps(func)通过functools.update_wrapper()将原函数中的部分内省属性固定,只传递部分关键参数来实现这个功能,感兴趣的读者可以自行进一步研究。
至此,我想你应该明白为什么需要现装饰器、什么是装饰器、以及如何写一个简单的装饰器,当你再次看到装饰器时,脑海中浮现的概念应该不仅仅是@。有关装饰器更高级的用法,以及一些常见、好用的装饰器,我将在装饰器的第二篇文章中进行介绍!
数据分析咨询请扫描二维码
若不方便扫码,搜微信号:CDAshujufenxi
数据分析在当今信息时代发挥着重要作用。单因素方差分析(One-Way ANOVA)是一种关键的统计方法,用于比较三个或更多独立样本组 ...
2025-04-25CDA持证人简介: 居瑜 ,CDA一级持证人国企财务经理,13年财务管理运营经验,在数据分析就业和实践经验方面有着丰富的积累和经 ...
2025-04-25在当今数字化时代,数据分析师的重要性与日俱增。但许多人在踏上这条职业道路时,往往充满疑惑: 如何成为一名数据分析师?成为 ...
2025-04-24以下的文章内容来源于刘静老师的专栏,如果您想阅读专栏《刘静:10大业务分析模型突破业务瓶颈》,点击下方链接 https://edu.cda ...
2025-04-23大咖简介: 刘凯,CDA大咖汇特邀讲师,DAMA中国分会理事,香港金管局特聘数据管理专家,拥有丰富的行业经验。本文将从数据要素 ...
2025-04-22CDA持证人简介 刘伟,美国 NAU 大学计算机信息技术硕士, CDA数据分析师三级持证人,现任职于江苏宝应农商银行数据治理岗。 学 ...
2025-04-21持证人简介:贺渲雯 ,CDA 数据分析师一级持证人,互联网行业数据分析师 今天我将为大家带来一个关于用户私域用户质量数据分析 ...
2025-04-18一、CDA持证人介绍 在数字化浪潮席卷商业领域的当下,数据分析已成为企业发展的关键驱动力。为助力大家深入了解数据分析在电商行 ...
2025-04-17CDA持证人简介:居瑜 ,CDA一级持证人,国企财务经理,13年财务管理运营经验,在数据分析实践方面积累了丰富的行业经验。 一、 ...
2025-04-16持证人简介: CDA持证人刘凌峰,CDA L1持证人,微软认证讲师(MCT)金山办公最有价值专家(KVP),工信部高级项目管理师,拥有 ...
2025-04-15持证人简介:CDA持证人黄葛英,ICF国际教练联盟认证教练,前字节跳动销售主管,拥有丰富的行业经验。在实际生活中,我们可能会 ...
2025-04-14在 Python 编程学习与实践中,Anaconda 是一款极为重要的工具。它作为一个开源的 Python 发行版本,集成了众多常用的科学计算库 ...
2025-04-14随着大数据时代的深入发展,数据运营成为企业不可或缺的岗位之一。这个职位的核心是通过收集、整理和分析数据,帮助企业做出科 ...
2025-04-11持证人简介:CDA持证人黄葛英,ICF国际教练联盟认证教练,前字节跳动销售主管,拥有丰富的行业经验。 本次分享我将以教培行业为 ...
2025-04-11近日《2025中国城市长租市场发展蓝皮书》(下称《蓝皮书》)正式发布。《蓝皮书》指出,当前我国城市住房正经历从“增量扩张”向 ...
2025-04-10在数字化时代的浪潮中,数据已经成为企业决策和运营的核心。每一位客户,每一次交易,都承载着丰富的信息和价值。 如何在海量客 ...
2025-04-09数据是数字化的基础。随着工业4.0的推进,企业生产运作过程中的在线数据变得更加丰富;而互联网、新零售等C端应用的丰富多彩,产 ...
2025-04-094月7日,美国关税政策对全球金融市场的冲击仍在肆虐,周一亚市早盘,美股股指、原油期货、加密货币、贵金属等资产齐齐重挫,市场 ...
2025-04-08背景 3月26日,科技圈迎来一则重磅消息,苹果公司宣布向浙江大学捐赠 3000 万元人民币,用于支持编程教育。 这一举措并非偶然, ...
2025-04-07在当今数据驱动的时代,数据分析能力备受青睐,数据分析能力频繁出现在岗位需求的描述中,不分岗位的任职要求中,会特意标出“熟 ...
2025-04-03