Python装饰器深入解析:从基础到高级应用
本文深入解析Python装饰器的核心原理与实战用法。从无需修改原代码即可扩展功能的角度出发,逐步演示如何构建通用装饰器、处理带参函数、支持不定长参数以及组合多个装饰器。通过具体代码案例,阐明装饰器的工作机制、执行顺序及常见错误排查,帮助开发者掌握这一Python高级特性。
Python装饰器本质上是一个返回值为函数的特殊函数,其主要作用是在不修改原函数源代码的前提下,为现有函数添加新的功能。这种机制极大地提升了代码的可维护性和复用性。
首先通过一个简单示例来引入概念:
def say():
print('Nice day')
say()
# 这个函数的输出为:
Nice day
假设当前代码输出Nice day,现在需要在该输出之前额外打印一行****************,期望效果如下所示:
****************
Nice day
常规思路下,可以直接修改原函数代码来实现这一需求:
def say():
print('****************')
print('Nice day')
say()
然而,如果后续发现需求变更,需要恢复原状,虽然当前仅增加了一行代码容易撤销,但若原函数逻辑复杂且长达百行,手动逐行撤销显然不现实。因此,我们需要寻找更优雅的解决方案:
def say():
print('Nice day')
def outer(): # 重新定义一个新函数
print('****************') # 处理新的逻辑
say() # 再调用原来的函数
outer()
# 现在的输出为:
'''
****************
Nice day
'''
上述方案看似满足了需求,但如果后续say()函数需要打印****************,而新加入的talk()函数也需要相同功能,难道要为每个函数单独编写一个outer()函数吗?这显然不符合DRY(Don't Repeat Yourself)原则,因此需要进一步优化:
def say():
print('Nice day')
def talk():
print('I am talk')
def outer(func): # 接收一个函数
print('****************') # 处理新的逻辑
func() # 调用传递过来的函数
outer(talk) # 把talk函数作为参数传递过去
# 输出结果如下:
'''
****************
I am talk
'''
此时,无论有多少函数需要打印****************,只需将函数名作为参数传递给outer()即可,这大大提升了便利性。但为了进一步简化调用过程,代码再次进行重构:
def say():
print('Nice day')
def outer(func):
def inner():
print('****************')
func() # 相当于 say()
return inner
s = outer(say) # 相当于 s = inner
s() # 相当于 inner()
推测此时的输出结果应与之前一致。这里的核心逻辑是将处理过程封装在内部函数中,调用outer(say)时,将say传入outer,获取返回值inner并赋值给s。此时s等价于inner,而s()等价于inner(),因此输出结果为:
**************
Nice day
这就是最基础的装饰器实现。不过,每次使用时仍需将结果赋值给变量(如这里的s),再通过s调用,这违反了“代码精简”的原则。因此,我们引入Python特有的语法糖进行简化:
def outer(func):
def inner():
print('****************')
func()
return inner
@outer # 用outer装饰say
def say():
print('Nice day')
say() # 调用say函数
这里将outer和say的定义顺序进行了调整,先定义outer函数。@outer语法表示使用outer来装饰say,这样直接调用say()即可实现先打印****************的功能。需要注意的是,如果不调换函数定义顺序,程序会抛出NameError: name 'outer' is not defined错误(因为作用域问题,outer尚未定义)。这算是相对复杂一点的装饰器写法。\n\n此时,读者可能会疑惑:上述示例均为无参函数,如果函数带有参数,或者参数数量不固定,该如何处理?Python结合灵活的设计完全可以解决:
# 带参数的装饰器
def outer(func):
def inner(name):
func(name)
return inner
@outer
def say(name):
print('name is %s.' % (name))
say('dandan')
# name is dandan.
上述示例中参数数量是固定的,但如果未来需求变化,需要额外传递hobby或age等参数,该如何应对?
# 带不定参数的装饰器
def outer(func):
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
@outer
def say(name, age):
print('name is %s, age is %d.' % (name, age))
@outer
def talk(name, age, hobby):
print('name is %s, age is %d, hobby is %s.' % (name, age, hobby))
say('dandan', 18)
talk('dandan', 18, 'Coding')
'''
name is dandan, age is 18.
name is dandan, age is 18, hobby is Coding.
'''
如果新增的功能较多,单个装饰器无法覆盖,是否可以同时使用多个装饰器?答案是肯定的:
# 多个装饰器
def outer(func):
def inner(*args, **kwargs):
print('****************')
func(*args, **kwargs)
return inner
def outer2(func):
def inner2(*args, **kwargs):
print('这里有1w+新功能')
func(*args, **kwargs)
return inner2
@outer
@outer2
def say(name, age):
print('name is %s, age is %d.' % (name, age))
@outer
@outer2
def talk(name, age, hobby):
print('name is %s, age is %d, hobby is %s.' % (name, age, hobby))
say('dandan', 18)
talk('dandan', 18, 'Coding')
'''
****************
这里有1w+新功能
name is dandan, age is 18.
****************
这里有1w+新功能
name is dandan, age is 18, hobby is Coding.
'''
需要特别注意,多个装饰器同时作用时,执行顺序是从最外层的第一个装饰器开始,依次向内执行,最后才执行原函数本身。
至此,关于Python装饰器的深入讲解已完成。更多Python相关内容可查阅本站历史文章或继续浏览后续教程,感谢大家的支持!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









