Python装饰器inputAssert在类构造方法中执行时机异常的原因排查
问题原因与解决方案
咱们先搞清楚为什么输出顺序和你预期的不一样——核心是你混淆了装饰器的执行阶段。
执行顺序拆解
当Python运行你的代码时,执行流程是这样的:
- 首先加载模块中的所有顶层代码,包括装饰器的定义、
Demo类的定义。 - 解析
Demo类内部的代码时,遇到@inputAssert(name=str):- 先立即调用
inputAssert(name=str),返回内层的decorate函数。 - 接着把
__init__方法作为参数传给decorate,执行decorate(func)——这时候你写的print('input assert')就直接执行了! - 最后
decorate返回wrapper函数,把原来的__init__替换成这个wrapper。
- 先立即调用
- 等类定义完成后,才会执行
if __name__ == '__main__':里的代码,这时候实例化Demo对象,才会调用wrapper,进而执行原来的__init__。
所以input assert会在类加载时就输出,而不是等到实例化的时候。
如何达到预期的输出顺序
如果你希望input assert在调用__init__(也就是实例化对象)时才打印,只需要把print('input assert')移到最内层的wrapper函数里就行——因为只有wrapper是在被装饰的函数被调用时才会执行的:
from functools import wraps from inspect import signature def inputAssert(*ty_args, **ty_kwargs): def decorate(func): # 这部分代码在类定义阶段执行(装饰时) sig = signature(func) bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments @wraps(func) def wrapper(*args, **kwargs): print('input assert') # 移到这里,调用__init__时执行 bound_values = sig.bind(*args, **kwargs) for name, value in bound_values.arguments.items(): if name in bound_types: if not isinstance(value, bound_types[name]): raise TypeError( 'Argument {} must be {}'.format(name, bound_types[name]) ) return func(*args, **kwargs) return wrapper return decorate class Demo: @inputAssert(name=str) def __init__(self, name=''): print('base model init') if __name__ == '__main__': print('------ base model init start----') d = Demo(name='xx') print('------ base model init finish----')
修改后运行,输出就会和你预期的一致:
------ base model init start---- input assert base model init ------ base model init finish----
额外提醒
装饰器的三层结构各有不同的执行时机:
- 最外层
inputAssert(*ty_args, **ty_kwargs):在你写@inputAssert(...)时立即执行,用来接收装饰器的参数。 - 中间层
decorate(func):在装饰目标函数/方法时执行,用来处理被装饰的函数,生成包装器。 - 最内层
wrapper(*args, **kwargs):每次调用被装饰的函数/方法时执行,这才是真正包裹原函数逻辑的地方。
内容的提问来源于stack exchange,提问作者Qiang Zhang
相关产品推荐
相关产品推荐

