使用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
相关产品推荐
相关产品推荐

