You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 10:40:26