Есть ли способ статически разрешить эту круговую зависимость? - PullRequest
0 голосов
/ 08 апреля 2020

У меня есть несколько python классов, которые связаны друг с другом, они пытаются имитировать c схему 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)

тогда я могу сделать что-то вроде этого:

file: 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'))

Это решение работает, но теряет возможность заранее знать типы полей, и я думаю, что это медленнее при построении сложных отношений (сотни предметов с объектами глубиной в несколько уровней).

Вот почему я ищу лучшие способы решения этой проблемы, есть идеи?

1 Ответ

2 голосов
/ 11 апреля 2020

Предполагая, что вы используете 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...