有没有办法静态解决这种循环依赖?

Is there a way to resolve statically this circular dependencies?

我有一些 python 类 相互关联,它们试图模仿 graphql 模式(模式本身不相关,我 post 这里的基本情况重现问题)。

GraphQL 架构如下所示:

type User {
  name: String
  orders: [Order]
}

type Order {
  key: String
  user: User
}

从模式设计的角度来看,这个模式没有任何问题,它是一个有效的模式,我已经有一个数据库运行这个关系(它只是意味着:一个用户可能有几个订单, 一个订单可能只有一个创建它的用户)。

在事情的python 方面,事情变得有点混乱。

我希望以下代码能够工作:

文件:models/Model.py

import attr

@attr.s
class Model():
  pass # Model internal workings not relevant to the example

文件:models/User.py

from typing import List
import attr
from . import Model

@attr.s
class User(Model):
  name: str = 'Name'
  orders: List[Order] = attr.ib(factory=list)

文件:models/Order.py

import attr
from . import Model

@attr.s
class Order(Model):
  key: str = 'abc'
  user: User = attr.ib(factory=User)

那么我可以这样做:

文件:main.py

import models as m
user = m.User.query(name='John', with='orders')
user.name # "John"
user.orders # [m.Order(key='1'), m.Order(key='2'), m.Order(key='3')...]
order = m.Order.query(key='1', with='user')
order.key # "1"
order.user # m.User(name="John")

由于循环依赖(用户需要提前定义订单类型,而订单需要用户),此代码不起作用。

我发现的解决方法是使用 importlib 延迟导入模型:

# current solution:
# using the importlib to import dynamically

from typing import List
import attr
from helpers import convert_to, list_convert_to, 

# Note: "convert_to" receives a class name and returns a function to instantiate it dinamically

@attr.s
class Model():
  pass

@attr.s
class User(Model):
  name: str = 'Name'
  orders: List[Model] = attr.ib(factory=list_convert_to('Order'))

@attr.s
class Order(Model):
  key: str = 'abc'
  user: Model = attr.ib(factory=list_convert_to('User'))

此解决方案有效,但失去了预先知道字段类型的能力,而且我认为在构建复杂关系时速度较慢(数百个项目具有几层深的对象)。

这就是为什么我正在寻找更好的方法来解决这个问题,有什么想法吗?

假设您使用的是 Python 3.7 或更高版本,以下行将使其工作:

from __future__ import annotations

它还允许您在定义 class 时引用它。例如

class C:
    @classmethod
    def factory(cls) -> C:
        ...

现在有效。

如果您的 classes 在多个文件中定义并且您因此获得循环依赖,您可以使用

保护导入
from typing import TYPE_CHECKING

# ...

if TYPE_CHECKING:
    from module import User