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

时间:2018-10-06 07:17:37

标签: python python-3.x circular-dependency python-dataclasses

假设class A的成员类型为class B,而class B的成员类型为class A

在Scala或Kotlin中,在这种情况下,您可以以任何顺序定义类而不必担心,因为即使在case / data类中,第一个定义的类也可以照常使用第二个定义的类。

但是在Python中,以下代码

class A:
    b = B()

class B:
    a = A()     

因为定义class B时未定义class A,所以引发了编译错误。

您可以解决这种简单的情况,例如this answer

class A:
    pass

class B:
    a = A()

A.b = B()

但是,这种方法不适用于Python中的数据类,因为在定义数据类后分配成员将不会更新数据类的自动生成的方法,这使“数据类”的使用变得无用。

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

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

如何避免这个问题?

3 个答案:

答案 0 :(得分:8)

有几种解决循环依赖项的方法,请参见Type hints: solve circular dependency

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

但是,“转发声明”该类可能会更容易:

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

答案 1 :(得分:4)

只有在我们将字段dataclass注入到b中之后,才可以应用A装饰器来实现您的目标。为此,我们只需要在A的{​​{1}}字段中添加类型注释

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

__annotations__

关于此方法的安全性和有效性,PEP 524指出

  在模块或类级别的

..,如果要注释的项目是简单名称,则它和注释将存储在该模块或类的__annotations__属性中。   [此属性]是可写的,因此允许:

     

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

因此以后通过编辑__annotations__['s'] = str添加类型注释与在类定义中定义它相同。

答案 2 :(得分:2)

由于python是脚本语言-无法用@dataclass来实现。因为python中没有“自动装配”(依赖注入)机制。 此时,如果您需要循环依赖-您应该正常使用类之一。

class A:
    b = None

@dataclass
class B:
    a: A

a = A()
a.b = B(a)

Python编译器遍历每一行,而无需从类/函数定义中跳转。并且当编译器/解释器看到下面的行b: B并且之前没有看到B类时-它将抛出异常NameError: name 'B' is not defined

我想相信有办法做到这一点(@dataclass的循环依赖),但事实是残酷的。 (您可以用Java /其他语言做很多事情,而不能用python做很多事情。这句话的另一个方向也很真实。)