Circular Reference Factory Method#
有的时候我们会在两个模块中定义两个不同的类. 如果在需要 type hint 的场合就可以用 if typing.TYPE_CHECKING:
的技巧引入 type hint. 但有的时候, 我们需要让两个类互相转化. 例如, 让 A 类的实例 + 上一些数据变成 B 类. 这种情况有下面几种做法:
是在 A 中创建一个普通方法
def to_b(self) -> "B"
是在 B 中创建一个
@classmethod
def from_a(cls, a: "A") -> "B"
.是单独创建两个函数
def from_a_to_b(a: "A") -> "B":
和def from_b_to_a(b: "B") -> "A":
.
这具体该用哪个方法呢?
这里我们首先强调一下前提条件. 如果我们把两个类放在一个模块内, 则不存在循环引用的问题, 那么我们怎么做都可以.
然后我们思考一下, 如果我们用 3 的话. 那显然是可以的. 只不过我们在使用 A, B 两个类的时候需要单独 import 这两个函数, 比较麻烦. 那有没有只用到 A, B 两个类的方法, 又能避免循环引用呢?
答案是用工厂函数. 也就是说, 你不要实现 def to_a(self, ...)
, def to_b(self, ...)
, 而是实现 def from_a(cls, ...)
, def from_b(cls, …)`` 这两个工厂函数. 我们拿 A 类举例, 我们需要把 B 转化为 A. 那么就可以实现一个 def from_b(cls, b: "B")
的 @classmethod
. 里面的 b 的 type hint 可以继续用 if typing.TYPE_CHECKING:
的技巧, 不需要真正的 import. 如果你要给 B 类实现 def to_a(self, ...)
, 这样就必须真正 import A, 因为你需要用 A 这个类来初始化. 这样就会导致循环引用. 下面的三个模块共同给出了示例代码.
module_a.py
1# -*- coding: utf-8 -*-
2# content of module_a.py
3
4import typing as T
5import dataclasses
6
7if T.TYPE_CHECKING:
8 from module_b import B
9
10
11@dataclasses.dataclass
12class A:
13 name: str = dataclasses.field()
14
15 def to_b(self) -> "B":
16 from module_b import B
17
18 return B(name=self.name)
19
20 @classmethod
21 def from_b(cls, b: "B"):
22 return cls(name=b.name)
module_b.py
1# -*- coding: utf-8 -*-
2# content of module_b.py
3
4import typing as T
5import dataclasses
6
7if T.TYPE_CHECKING:
8 from module_a import A
9
10
11@dataclasses.dataclass
12class B:
13 name: str = dataclasses.field()
14
15 def to_a(self) -> "A":
16 from module_a import A
17
18 return A(name=self.name)
19
20 @classmethod
21 def from_a(cls, a: "A"):
22 return cls(name=a.name)
test.py
1# -*- coding: utf-8 -*-
2
3from module_a import A
4from module_b import B
5
6a = A(name="alice")
7b = B(name="bob")
8
9print(f"{a.to_b() = }")
10print(f"{A.from_b(b) = }")
11print(f"{b.to_a() = }")
12print(f"{B.from_a(a) = }")