Python类继承字段的副作用:未使用字段值残留问题
解决Python类实例共享类级别Field属性值的问题
我们采用一套简化类结构处理遗留系统的数据清洗与结构化,其中ProductModel包含Field类型字段,Field类负责执行数据清洗逻辑。当前方案适配业务场景,但存在问题:创建新ProductModel实例时,未赋值的字段会保留前一个实例的旧值。
可复现问题的代码
class BaseManager: database_settings = {} def __init__(self, model_class): self.model_class = model_class def build_query(self): pass def connection(self): pass def run_query(self): pass def select(self): pass class MetaModel(type): manager_class = BaseManager def __new__(mcs, name, bases, attrs): field_list = [] for k, v in attrs.items(): if isinstance(v, Field): v.field_name = k v.table_name = attrs.get('table_name') field_list.append(k) cls = type.__new__(mcs, name, bases, attrs) cls.__field_list__ = field_list return cls def _get_manager(cls): return cls.manager_class(model_class=cls) @property def objects(cls): return cls._get_manager() class Field: def __init__(self, field_name, value=None): self.field_name = field_name self.value = value def set_value(self, value): self.value = value class ProductModel(metaclass=MetaModel): sku = Field('sku') name = Field('name') table_name = 'my_table' def __init__(self, **field_data): for field_name, value in field_data.items(): getattr(self, field_name).set_value(value) def __str__(self): return f"{self.sku.value=}, {self.name.value=}"
测试示例
第一个实例:
prod = ProductModel(sku='124', name='Name') print(prod) # 输出:self.sku.value='124', self.name.value='Name'
结果符合预期。
第二个实例:
prod_two = ProductModel(sku='789') print(prod_two) # 输出:self.sku.value='789', self.name.value='Name'
sku更新正确,但name保留了前一个实例的旧值,未重置为None。
问题根源
Field实例是ProductModel的类属性,所有ProductModel实例共享同一个Field对象。当第一个实例调用set_value修改Field的value时,修改的是类属性里的Field对象的属性值,后续实例访问该Field时,自然会拿到被修改过的值。
解决方案:用描述符模式重构Field类
这是Python处理实例属性隔离的标准方式,让每个实例的字段值独立存储,不再依赖类级别Field对象的属性。
修改后的完整代码
class BaseManager: database_settings = {} def __init__(self, model_class): self.model_class = model_class def build_query(self): pass def connection(self): pass def run_query(self): pass def select(self): pass class MetaModel(type): manager_class = BaseManager def __new__(mcs, name, bases, attrs): field_list = [] for k, v in attrs.items(): if isinstance(v, Field): v.field_name = k v.table_name = attrs.get('table_name') field_list.append(k) cls = type.__new__(mcs, name, bases, attrs) cls.__field_list__ = field_list return cls def _get_manager(cls): return cls.manager_class(model_class=cls) @property def objects(cls): return cls._get_manager() # 重构Field为描述符类 class Field: def __init__(self, field_name, value=None): self.field_name = field_name self.default_value = value # 存储默认值,而非实例的具体值 def __get__(self, instance, owner): # 从实例自身的__dict__取值,不存在则返回默认值 if instance is None: return self return instance.__dict__.get(self.field_name, self.default_value) def __set__(self, instance, value): # 将值存储到实例自身的__dict__中 instance.__dict__[self.field_name] = value # 兼容原有set_value逻辑(可选) def set_value(self, instance, value): self.__set__(instance, value) class ProductModel(metaclass=MetaModel): sku = Field('sku') name = Field('name') table_name = 'my_table' def __init__(self, **field_data): # 直接赋值或调用set_value(二选一即可) for field_name, value in field_data.items(): # 方式1:直接赋值(推荐) setattr(self, field_name, value) # 方式2:调用兼容的set_value # getattr(self, field_name).set_value(self, value) def __str__(self): return f"{self.sku=}, {self.name=}"
测试验证
prod = ProductModel(sku='124', name='Name') print(prod) # 输出:self.sku='124', self.name='Name' prod_two = ProductModel(sku='789') print(prod_two) # 输出:self.sku='789', self.name=None
方案说明
Field现在是描述符类,类级别Field对象仅管理字段元信息(默认值、字段名),实例的具体值存储在每个实例自身的__dict__中,不同实例完全隔离。- 无需在
__init__中手动重置所有字段,新实例未赋值的字段会自动使用默认值None。
内容的提问来源于stack exchange,提问作者S.D.
相关产品推荐
相关产品推荐

