Python装饰器入门:从原理到实战的完整指南
本文深入解析Python装饰器的核心机制,从装饰器模式、一等公民函数及闭包概念入手,逐步演示如何构建支持参数、返回值及类装饰器的完整方案。通过代码实例展示@语法糖原理,帮助开发者掌握这一强大的Python高级特性,提升代码复用性与可维护性。
核心概念解析
要深入理解Python中的装饰器,建议从基础概念入手:
装饰器模式:该模式的核心在于“在不修改原有内部实现的前提下,为函数或类扩展特定功能”。这使得我们可以将通用且与业务逻辑无关的代码抽象出来,作为装饰器附加到目标函数或类上。从面向切面编程(AOP)的角度看,装饰器本质上就是一个切面。
函数是一等公民:这意味着函数可以像普通变量一样被操作。在Python中,你可以将函数赋值给变量,将其作为参数传递给其他函数,或者作为其他函数的返回值返回。
闭包:通常我们知道局部作用域可以访问全局变量,类似地,当一个函数内部定义了另一个函数时,内部函数可以访问外部函数作用域中的变量,这种现象称为闭包。
从最简单的装饰器开始实践
理解上述概念后,我们尝试利用这些特性实现一个基础装饰器。
首先明确需求:有时我们需要在函数调用时打印日志。虽然可以在每个需要日志的函数中嵌入打印代码,但这会导致大量重复代码,并增加业务代码与无关逻辑的耦合度。因此,我们需要一个装饰器,在函数调用时自动打印日志以记录调用行为。
假设我们有以下业务函数foo:
def foo():
print('in function foo')
我们设想通过调用foo = deco(foo)来为函数foo增加打印日志功能,同时不影响其原有业务逻辑。在此设想下,装饰器deco本身应是一个函数,它接收另一个函数作为输入,并返回一个新的、经过装饰的函数。在Python中,实现方式如下:
def deco(func): # 接收一个函数作为参数
def new_func():
print(f'[log] run function {func.__name__}') # 此处使用了Python3.6的格式化字符串
func() # 闭包,在内部函数中使用了外部函数的变量
return new_func # 将新函数作为返回值返回
执行测试效果如下:
>>> foo = deco(foo) >>> foo() [log] run function foo in function foo
至此,我们成功实现了一个最简单的装饰器。在上述代码中,装饰器deco接收任意函数作为参数,并在内部构造另一个函数。利用闭包特性,新函数可以调用存在于装饰器deco局部作用域中的函数func。
神奇的@语法糖
按照上述写法,每次都需要手动为被装饰函数赋新值。如果函数或装饰器数量增加,手动编写赋值和函数调用将变得繁琐。Python提供了更优雅的写法,只需一个@符号。
在Python中,使用装饰器的标准写法如下:
def deco(func):
def new_func():
print(f'[log] run function {func.__name__}')
func()
return new_func
@deco
def foo():
print('in function foo')
此处省略了显式的函数赋值,直接在函数foo定义的上一行添加@deco进行装饰。运行测试:
>>> foo() [log] run function foo in function foo
这看起来神奇,实则并无魔法。Python在处理函数定义代码时,自动将foo=deco(foo)的逻辑插入其中。
装饰器如何接收参数
上述代码实现了在业务函数调用前打印日志的功能。若需要在业务代码执行后打印自定义消息,必须让装饰器支持接收自定义参数。
前文提到,Python仅在遇到@deco时,将调用deco(func)的结果赋值给被装饰函数。因此,当需要带参数的装饰器时,代码应写为@deco('some message'),此时Python将调用deco(msg)(func)的结果赋值给foo。逻辑变得简单:只需在原有代码基础上嵌套一层函数:
def deco(msg):
def inner_deco(func):
def new_func():
print(f'[log] run function {func.__name__}')
func()
print(f'[log] {msg}')
return new_func
return inner_deco
@deco('some message')
def foo():
print('in function foo')
执行测试效果如下:
>>> foo() [log] run function foo in function foo [log] some message
不支持带参数被装饰函数的装饰器是不完善的
上述代码仍存在缺陷,因为我们仅考虑了函数foo无参数的情况。若函数foo带有参数,该装饰器将丢失参数信息,这不符合合格装饰器的标准。因此,我们借助Python中的*args和**kwargs使被装饰函数支持传入任意参数:
def deco(msg):
def inner_deco(func):
def new_func(*args, **kwargs):
print(f'[log] run function {func.__name__}')
func(*args, **kwargs)
print(f'[log] {msg}')
return new_func
return inner_deco
@deco('some message')
def foo(a, b=None):
print('in function foo')
print(f'a is {a} & b is {b}')
这样,无论函数foo的参数列表如何,均能正常工作:
>>> foo('hello')
[log] run function foo
in function foo
a is hello & b is None
[log] some message
不支持有返回值被装饰函数的装饰器是不完善的
别忘了,此前编写的函数foo均无返回值。若函数foo有返回值,处理方式如下:
def deco(msg):
def inner_deco(func):
def new_func(*args, **kwargs):
print(f'[log] run function {func.__name__}')
rlt = func(*args, **kwargs)
print(f'[log] {msg}')
return rlt
return new_func
return inner_deco
@deco('some message')
def foo(a, b=None):
print('in function foo')
print(f'a is {a} & b is {b}')
return 'ok'
由于装饰器在原函数执行完毕后还需执行其他操作,因此应暂存返回值,待装饰器逻辑执行完毕后再返回最终结果。这就是我们的最终版装饰器:
>>> rlt = foo('a')
[log] run function foo
in function foo
a is a & b is None
[log] some message
>>> print(rlt)
ok
是否有更高级的用法?
当然有,既然标题已提及,必然存在更高级的操作。
在Python中,你可以使用类作为装饰器:
class Deco(object):
def __call__(self, func):
def new_func():
print(f'[log] run function {func.__name__}')
func()
return new_func
@Deco()
def foo():
print('in function foo')
>>> foo() [log] run function foo in function foo
这样做的好处是可以利用类更好地管理参数和调用逻辑,相比之前的三层函数嵌套形式,代码结构更加清晰。
在Python中,你还可以使用装饰器来装饰一个类,例如:
def add_doc(doc):
def deco(cls):
cls.__doc__ = doc
return cls
return deco
@add_doc('this is the doc of Cls')
class Cls(object):
pass
查看效果:
>>> help(Cls) Help on class Cls in module __main__: class Cls(builtins.object) | this is the doc of Cls
上述代码仅为示例,展示装饰器如何应用于类。在实际开发中,对类的扩展通常通过继承而非装饰器实现。
如何巧妙利用装饰器,取决于开发者的想象力。
总结
以上即为本文关于快速了解Python中装饰器的全部内容,希望对你有所帮助。感兴趣的朋友可继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









