【问题标题】:Is there a way to resolve statically this circular dependencies?有没有办法静态解决这种循环依赖?
【发布时间】:2020-07-20 10:45:57
【问题描述】:

我有一些相互关联的 python 类,它们试图模仿 graphql 模式(模式本身不相关,我在这里发布基本案例以重现问题)。

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)

那么我可以这样做:

文件:ma​​in.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 python-3.x oop graphql python-attrs


    【解决方案1】:

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

    from __future__ import annotations
    

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

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

    现在可以使用了。

    如果您的类在多个文件中定义,并且因此导致循环依赖,您可以使用保护导入

    from typing import TYPE_CHECKING
    
    # ...
    
    if TYPE_CHECKING:
        from module import User
    

    【讨论】:

    • 即使没有annotations,您也可以简单地使用字符串'C' 代替引用C。 (这几乎就是 annotations 所做的所有事情:推迟对表达式的评估并将其保存为字符串。)
    • @hynek,如果类在同一个文件中定义,这似乎工作正常,但当每个类都在自己的文件中时仍然失败:<Model>.py。有没有解决的办法?每个文件的顶部都有自己的from __future__ import annotations
    • @chepner 我需要具体类型,而不是字符串,因为我稍后会使用它来执行类型转换(我不确定我是否遵循您的建议)
    • @BarucAlmaguer 我已经更新了答案:有一个结构,但它与您对具体类的要求相冲突。不幸的是,这就是 Python 的类型附加与导入的工作方式发生冲突的地方。
    • @hynek 当你说:“......但它与你对具体课程的要求相冲突” 你是什么意思? TYPE_CHECKING 不适用于此示例?如果是这样,有没有办法重构这些类以允许这种类型的保护导入,或者您是否建议将所有模型包含在同一个文件中? (模型文件是用脚本构建的,所以如果这是最佳实践,重构起来很容易)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-22
    • 2017-04-27
    • 1970-01-01
    • 2020-11-12
    相关资源
    最近更新 更多