实例方法默认返回值包装器:类内装饰器实现及优化问询
问题描述
我有十几个实例方法,这些方法仅在满足特定条件时执行核心逻辑,否则返回各自不同的默认值,初始实现如下:
def fun(self, *args, **kwargs): if not self.init(): return ... # <- 默认值,每个方法都不同 # 方法核心逻辑 return ... # 业务返回值
我通过装饰器实现了该逻辑,代码如下:
from functools import partial, wraps def check_init(caller = None, *, ret = None): if caller is None: return partial(check_init, ret = ret) @wraps(caller) def _check_init(self, *args, **kwargs): return caller(self, *args, **kwargs) if self.init() else ret return _check_init class Test: num: int = 2 def init(self) -> bool: return self.num < 5 @check_init(ret = 'Cannot show number') def show(self) -> str: return 'Number is %d' % self.num x = Test() print(x.show()) # "Number is 2" x.num = 4 print(x.show()) # "Number is 4" x.num = 6 print(x.show()) # "Cannot show number"
现在有两个技术问题:
- 由于该逻辑仅与Test类的实例方法相关,如何将完整的check_init装饰器代码块放入Test类内部?放在类外时无法为self添加Test或Self类型提示。
- 是否存在更符合Python风格(Pythonic)的实现方式?最好仍使用方法装饰器。
解决方案
1. 将装饰器放入类内部并完善类型提示
可以把装饰器定义为类方法嵌入Test类中,结合Self类型(从typing导入)标注self参数,既能让装饰器归属于类内部逻辑,又能解决类型提示缺失的问题。
示例代码:
from functools import partial, wraps from typing import Self, Callable, Any class Test: num: int = 2 @classmethod def check_init(cls, caller: Callable[..., Any] | None = None, *, ret: Any = None) -> Callable: if caller is None: return partial(cls.check_init, ret=ret) @wraps(caller) def _check_init(self: Self, *args: Any, **kwargs: Any) -> Any: return caller(self, *args, **kwargs) if self.init() else ret return _check_init def init(self) -> bool: return self.num < 5 @check_init(ret='Cannot show number') def show(self) -> str: return 'Number is %d' % self.num x = Test() print(x.show()) # Number is 2 x.num = 4 print(x.show()) # Number is 4 x.num = 6 print(x.show()) # Cannot show number
2. 更Pythonic的装饰器实现
基于方法装饰器的前提下,可通过类型参数化和清晰的嵌套结构优化代码,让类型提示更精准、可读性更强:
from functools import wraps from typing import Self, Callable, ParamSpec, TypeVar P = ParamSpec('P') R = TypeVar('R') class Test: num: int = 2 @classmethod def check_init(cls, default_ret: Any) -> Callable[[Callable[P, R]], Callable[P, R | Any]]: def decorator(func: Callable[P, R]) -> Callable[P, R | Any]: @wraps(func) def wrapper(self: Self, *args: P.args, **kwargs: P.kwargs) -> R | Any: return func(self, *args, **kwargs) if self.init() else default_ret return wrapper return decorator def init(self) -> bool: return self.num < 5 @check_init(default_ret='Cannot show number') def show(self) -> str: return 'Number is %d' % self.num
这个版本用ParamSpec和TypeVar保留原方法的参数与返回值类型,类型检查更严谨;同时拆分装饰器嵌套结构,逻辑层级更清晰,符合Python的可读性优先原则。
内容的提问来源于stack exchange,提问作者Marko Gulin
相关产品推荐
相关产品推荐

