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

使用attrs框架时,如何正确实现类作为另一个类的实例属性?

attrs/dataclasses 中嵌套类属性的最佳实践

问题核心

用常规类写法时,每个Person实例会独立创建Address对象:

class Address:
    def __init__(self) -> None:
        self.street = None

class Person:
    def __init__(self, name) -> None:
        self.name = name
        self.address = Address()

但直接用 attrs 写默认值时,会出现所有Person实例共享同一个Address的问题:

from attrs import define

@define
class Address:
    street: str | None = None

@define
class Person:
    name: str
    address: Address = Address()  # 类定义时就创建了Address实例,所有Person共享

测试代码会出现不符合预期的结果:

person_1 = Person("Joe")
person_2 = Person("Jane")

person_1.address.street = "street"
person_2.address.street = "other_street"

print(person_1.address.street)  # 输出 other_street,而非预期的 street

原因是 attrs/dataclasses 的默认值是类级别初始化的,可变对象(比如自定义类实例)会被所有实例共享,和常规类在__init__中动态创建实例的逻辑不同。

最佳实践方案

1. 使用field(factory=...)(最简洁推荐)

attrs 提供的factory参数可以指定一个可调用对象,每次实例化Person时都会调用该对象创建新的Address实例,完美匹配常规类的行为:

from attrs import define, field

@define
class Address:
    street: str | None = None

@define
class Person:
    name: str
    address: Address = field(factory=Address)  # 每次实例化Person都新建Address

测试后结果和常规类一致,每个Person拥有独立的Address:

person_1 = Person("Joe")
person_2 = Person("Jane")

person_1.address.street = "street"
person_2.address.street = "other_street"

print(person_1.address.street)  # 输出 street,符合预期

2. __attrs_post_init__(自定义逻辑场景)

如果需要在初始化Address时添加自定义逻辑(比如动态传入参数),可以用这个方法,适合复杂场景:

from attrs import define, field

@define
class Address:
    street: str | None = None
    city: str | None = None

@define
class Person:
    name: str
    address: Address = field(init=False)

    def __attrs_post_init__(self):
        # 可在这里添加自定义逻辑,比如根据name设置默认城市
        self.address = Address(city="Beijing")

3. 默认值设为None,后置初始化补全

如果需要支持“外部传入自定义Address”和“默认创建空实例”两种场景,可以用这种方式:

from attrs import define, field

@define
class Address:
    street: str | None = None

@define
class Person:
    name: str
    address: Address | None = None

    def __attrs_post_init__(self):
        if self.address is None:
            self.address = Address()

使用时既可以传入自定义实例,也可以依赖默认行为:

# 传入自定义Address
custom_addr = Address(street="Custom Street")
person = Person("Alice", address=custom_addr)

# 使用默认空Address
person_default = Person("Bob")
person_default.address.street = "New Street"

注意事项

这本质是 Python 可变默认参数的经典陷阱——默认值在类定义阶段就会被计算,而非每次实例化时计算。所以对于列表、字典、自定义类实例这类可变对象,绝对不能直接作为 attrs/dataclasses 的默认值,否则会引发实例间共享对象的意外副作用。

内容的提问来源于stack exchange,提问作者Jan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:35:21