如何在 Python 3.7+ 中定义循环依赖数据 类?

How to define circularly dependent data classes in Python 3.7+?

假设class A有一个类型为class B的成员,class B有一个类型为class A的成员。

在 Scala 或 Kotlin 中,在这种情况下,您可以按任何顺序定义 classes 而无需担心,因为第一个定义的 class 可以使用第二个定义的 class 作为通常,即使在 case/data classes.

然而在Python,下面的代码

class A:
    b = B()

class B:
    a = A()     

抛出编译错误,因为在定义 class A 时未定义 class B

您可以解决这个简单的问题,例如 this answer

class A:
    pass

class B:
    a = A()

A.b = B()

但是,这种方式不适用于Python中的数据classes,因为在定义数据classes之后分配成员不会更新自动生成的方法数据 classes,这使得 "data class" 的使用变得毫无用处。

@dataclass
class A:
    b: B  # or `b: Optional[B]`

@dataclass
class B:
    a: A  # or `a: Optional[A]`

如何避免这个问题?

只有在我们将字段 b 注入 A 之后,您才能通过应用 dataclass 装饰器来实现您的目标。为此,我们只需将类型注释添加到 A__annotations__-field

以下代码解决了您的问题:

class A:
    b: None     # Note: __annotations__ only exists if >=1 annotation exists

@dataclass
class B:
    a: A

A.__annotations__.update(b=B) # Note: not the same as A.b: B
A = dataclass(A) # apply decorator

关于此方法的安全性和有效性,PEP 524声明

..at the module or class level, if the item being annotated is a simple name, then it and the annotation will be stored in the __annotations__ attribute of that module or class. [This attribute] is writable, so this is permitted:

__annotations__['s'] = str

因此,稍后通过编辑 __annotations__ 添加类型注释与在 class 定义中定义它是相同的。

有几种方法可以解决像这样的循环依赖,参见

您始终可以手动应用装饰器(并更新注释),如@Nearoo 的回答所示。

但是,"forward declare" class:

可能更容易
class A:
    pass

@dataclass
class B:
    a: A

@dataclass
class A:
    b: B

或者简单地使用前向引用:

@dataclass
class B:
    a: 'A'

@dataclass
class A:
    b: B

最干净的是导入Python 4.0's behavior(如果可以的话):

from __future__ import annotations

@dataclass
class B:
    a: A

@dataclass
class A:
    b: B

它更加冗长,但是对现场工厂使用 lambda 怎么样:

from __future__ import annotations

from dataclasses import dataclass, field


@dataclass
class A:
    b: B = field(default_factory=lambda: B())


@dataclass
class B:
    a: A = field(default_factory=lambda: A())


A()  # results in infinite recursion (as intended?)