有没有办法静态解决这种循环依赖?
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
我有一些 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