如何在 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?)
假设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?)