替代__getattr__()实现Python包装类的通用解决方案咨询
问题描述
无法通过继承让Swimmer类复用Person类的功能,必须用组合方式(Swimmer实例持有Person实例)实现类似继承的效果。原实现用__getattr__()转发属性,但存在严重问题:拼写错误时会误查Person实例的属性,导致bug排查难度极大。
核心需求
- 仅转发
Person的指定方法:kneel()、crawl()、walk()、lean_over()、lay_down() - 需要通用解决方案,可接受任意类输入,动态生成对应的包装类,而非仅适配
Swimmer和Person
原问题代码
class Person: pass class Swimmer: def __init__(self, person): self._person = person def __getattr__(self, attrname:str): try: attr = getattr(self._person) # 原代码存在参数缺失错误 return attr except AttributeError: raise AttributeError
类方法清单
Person类方法:kneel()、crawl()、walk()、lean_over()、lay_down()Swimmer类自有方法:run()、swim()、dive()、throw_ball()
痛点
__getattr__会拦截所有未找到的属性,比如拼写错误的self.oops会去self._person里查找,导致错误被隐藏,难以定位问题。
尝试过的无效方案
class _Swimmer: def __init__(self, person): self._person = person def run(self): return "I ran" def swim(self): return "I swam" def dive(self): # 此处self.oops不应去self._person中查找! self.oops return "I dove" def _getattrimp(self, attrname:str): # 拼写错误:将__getattr__写成_getattrimp,导致逻辑失效 try: attr = getattr(self._person) return attr except AttributeError: raise AttributeError class Swimmer(_Swimmer): def __getattr__(self, attrname:str): attr = self._getattrimp(attrname) return attr
疑问
- 如何编写能接受任意类输入、生成拥有该类指定同名方法的通用包装类?
- 是否适合通过动态创建
Swimmer的子类来实现需求?
解决方案
一、针对Swimmer的精确转发实现
要避免__getattr__的过度拦截,最直接的方式是显式定义需要转发的方法,拒绝自动转发逻辑。这样拼写错误的属性会直接抛出AttributeError,不会去Person实例中查找。
class Person: def kneel(self, *args, **kwargs): return "I KNEELED" def crawl(self, *args, **kwargs): return "I crawled" def walk(self, *args, **kwargs): return "I WALKED" def lean_over(self, *args, **kwargs): return "I leaned over" def lay_down(self, *args, **kwargs): return "I lay down" class Swimmer: def __init__(self, person): self._person = person # 显式转发指定的Person方法 def kneel(self, *args, **kwargs): return self._person.kneel(*args, **kwargs) def crawl(self, *args, **kwargs): return self._person.crawl(*args, **kwargs) def walk(self, *args, **kwargs): return self._person.walk(*args, **kwargs) def lean_over(self, *args, **kwargs): return self._person.lean_over(*args, **kwargs) def lay_down(self, *args, **kwargs): return self._person.lay_down(*args, **kwargs) # Swimmer自有方法 def run(self): return "I ran" def swim(self): return "I swam" def dive(self): # 此处self.oops会直接抛出AttributeError,不会去_person查找 # self.oops # 取消注释会立刻报错,符合预期 return "I dove" def throw_ball(self): return "I threw a ball"
该方案优点:逻辑明确、易维护,所有转发行为一目了然,拼写错误会立刻暴露,排查问题简单。
二、通用动态包装类生成方案
如果需要适配任意类和任意指定方法,可以编写工厂函数,动态生成包装类,自动为指定方法创建转发逻辑:
import functools def create_wrapper_class(base_class, forwarded_methods, wrapper_class_name="Wrapper", extra_methods=None): """ 动态生成包装类的工厂函数 :param base_class: 被包装的原始类 :param forwarded_methods: 需要转发的方法名列表 :param wrapper_class_name: 生成的包装类名称 :param extra_methods: 包装类额外拥有的方法(字典形式:key为方法名,value为方法实现) :return: 动态生成的包装类 """ class_attrs = {} # 添加初始化方法,持有原始类实例 def __init__(self, instance): self._wrapped_instance = instance class_attrs['__init__'] = __init__ # 为每个需要转发的方法创建转发逻辑 for method_name in forwarded_methods: if not hasattr(base_class, method_name): raise ValueError(f"类 {base_class.__name__} 不存在方法 {method_name}") def forwarded_method(self, *args, **kwargs): return getattr(self._wrapped_instance, method_name)(*args, **kwargs) # 保留原方法的元信息(如__name__、__doc__) forwarded_method = functools.wraps(getattr(base_class, method_name))(forwarded_method) class_attrs[method_name] = forwarded_method # 添加额外方法 if extra_methods: class_attrs.update(extra_methods) # 动态生成类 return type(wrapper_class_name, (object,), class_attrs)
使用示例
用该工厂函数生成Swimmer类:
# 定义Swimmer的额外方法 swimmer_extra_methods = { 'run': lambda self: "I ran", 'swim': lambda self: "I swam", 'dive': lambda self: "I dove", 'throw_ball': lambda self: "I threw a ball" } # 动态生成Swimmer类 Swimmer = create_wrapper_class( base_class=Person, forwarded_methods=['kneel', 'crawl', 'walk', 'lean_over', 'lay_down'], wrapper_class_name='Swimmer', extra_methods=swimmer_extra_methods ) # 使用方式与常规类一致 person = Person() swimmer = Swimmer(person) print(swimmer.kneel()) # 输出: I KNEELED print(swimmer.swim()) # 输出: I swam # swimmer.oops # 直接抛出AttributeError,符合预期
三、关于动态创建Swimmer子类的问题
不建议通过动态创建Swimmer子类的方式实现,原因如下:
- 复杂度高:动态子类会增加隐式逻辑,降低代码可读性和可维护性,排查问题时需要跟踪动态生成的类结构。
- 违背设计初衷:我们的需求是组合而非继承,动态子类会强行建立不必要的类层级。
- 灵活性不足:工厂函数方案更灵活,可直接生成独立的包装类,无需依赖现有父类结构。
相比之下,工厂函数方案更直接简洁,完全满足通用包装需求,同时保持代码清晰性。
内容的提问来源于stack exchange,提问作者Toothpick Anemone
相关产品推荐
相关产品推荐

