Python中为类批量添加相似采样方法的最佳实践及优化方案咨询
Python中为类批量添加相似采样方法的最佳实践及优化方案咨询
首先得说,你当前用装饰器封装采样逻辑的思路已经是非常符合Pythonic的好实践了——它完美遵循了DRY原则,把重复的插值、采样代码抽离到装饰器里,让业务方法和采样逻辑解耦,代码可读性和复用性都很强。唯一的小痛点就是需要手动给每个原方法写对应的spl_*方法,当类里的统计方法变多的时候,确实会有点繁琐。
下面给你几个更优雅的优化方向,你可以根据自己的实际场景来选:
方案1:自动批量生成采样方法
如果你能提前知道需要采样的方法列表,可以在类定义完成后,自动遍历这些方法并生成对应的采样版本,完全不用手动写spl_*方法:
from scipy import interpolate import numpy as np def sample(func): def wrapper(self, n_sample=100, interp_kwargs=None, *args, **kwargs): interp_kwargs = interp_kwargs or {} new_ids = np.linspace(self.ids[0], self.ids[-1], n_sample) vals = func(self, *args, **kwargs) interpolator = interpolate.interp1d(self.ids, vals, axis=0, **interp_kwargs) return interpolator(new_ids) return wrapper class Bar: def __init__(self, ids, data): self.ids = ids self.data = data def mean(self): return self.data.mean(axis=0) def sd(self): return self.data.std(axis=0) def max(self): return self.data.max(axis=0) def min(self): return self.data.min(axis=0) # 批量生成采样方法:维护一个需要采样的方法列表即可 methods_to_sample = ['mean', 'sd', 'max', 'min'] for method_name in methods_to_sample: original_method = getattr(Bar, method_name) setattr(Bar, f'spl_{method_name}', sample(original_method)) # 测试 bar = Bar(ids=np.arange(5), data=np.random.rand(10,5)) # 直接调用自动生成的spl_max方法 print(bar.spl_max(n_sample=50, interp_kwargs={'kind': 'cubic'}))
这种方式的好处是代码简洁,只需要维护一个方法列表,新增统计方法后,只要把名字加到列表里就行,非常高效。
方案2:用类装饰器整合逻辑
如果想把采样方法的生成逻辑和类定义更紧密地结合,可以用类装饰器来实现,可读性会更好:
def add_sampled_methods(method_list): def class_decorator(cls): for method_name in method_list: original_method = getattr(cls, method_name) setattr(cls, f'spl_{method_name}', sample(original_method)) return cls return class_decorator # 直接在类上添加装饰器,指定要采样的方法 @add_sampled_methods(['mean', 'sd', 'max', 'min']) class Bar: def __init__(self, ids, data): self.ids = ids self.data = data def mean(self): return self.data.mean(axis=0) def sd(self): return self.data.std(axis=0) def max(self): return self.data.max(axis=0) def min(self): return self.data.min(axis=0)
这种方式把批量生成的逻辑封装在装饰器里,类定义时一眼就能看到哪些方法会生成采样版本,代码的语义更清晰。
方案3:动态生成(最灵活)
如果你的类会动态新增方法,或者不想提前维护方法列表,可以用__getattr__魔法方法,在你第一次访问spl_*方法时自动生成并缓存采样方法:
class Bar: def __init__(self, ids, data): self.ids = ids self.data = data def mean(self): return self.data.mean(axis=0) def sd(self): return self.data.std(axis=0) def __getattr__(self, name): # 检测是否是采样方法的命名格式 if name.startswith('spl_'): original_method_name = name[4:] # 检查原方法是否存在 if hasattr(self, original_method_name): original_method = getattr(self, original_method_name) # 生成采样方法并缓存(避免每次调用都重新生成) sampled_method = sample(original_method) setattr(self, name, sampled_method) return sampled_method # 不是采样方法则抛出常规的属性错误 raise AttributeError(f"'Bar' object has no attribute '{name}'") # 测试:直接访问spl_mean,会自动生成方法 bar = Bar(ids=np.arange(5), data=np.random.rand(10,5)) print(bar.spl_mean()) # 新增一个方法后,也能直接用spl_前缀调用 bar.median = lambda: self.data.median(axis=0) print(bar.spl_median())
这种方式的灵活性拉满,完全不用提前配置,新增任意方法后,只要用spl_原方法名就能直接调用采样版本,适合方法数量多、变动频繁的场景。
一些细节优化
- 浮点精度问题:原代码里的
assert np.all(...)容易因为浮点插值的微小误差失败,建议换成np.allclose来做断言。 - 插值参数可配置:我在装饰器里加了
interp_kwargs参数,允许用户自定义插值的方式(比如三次样条kind='cubic'),让采样逻辑更灵活。
备注:内容来源于stack exchange,提问作者C.K.
相关产品推荐
相关产品推荐

