18

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

在 Scala 或 Kotlin 中,您可以在这种情况下以任何顺序定义类而无需担心,因为第一个定义的类可以照常使用第二个定义的类,即使在案例/数据类中也是如此。

但是在 Python 中,下面的代码

class A:
    b = B()

class B:
    a = A()     

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

您可以解决这个简单的案例,就像在这个答案中一样

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]`

我怎样才能避免这个问题?

4

3 回答 3

17

有几种方法可以解决这样的循环依赖关系,请参阅类型提示:解决循环依赖关系

您始终可以手动应用装饰器(并更新注释),例如@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 的行为(如果可以的话):

from __future__ import annotations

@dataclass
class B:
    a: A

@dataclass
class A:
    b: B
于 2018-10-08T09:30:04.117 回答
4

只有在我们将dataclass字段b注入A. 为此,我们只需将类型注释添加到A's __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指出

..在模块或类级别,如果被注释的项目是一个简单的名称,那么它和注释将存储在该模块或类的 __annotations__ 属性中。[此属性] 是可写的,所以这是允许的:

__annotations__['s'] = str

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

于 2018-10-08T09:16:58.570 回答
0

由于 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做。这句话的另一个方向也是真实的。)

于 2018-10-08T07:59:25.603 回答