Как определить циклически зависимые классы данных в Python 3. 7+?
Предположим, что class A
имеет член, тип которого является class B
, а class B
имеет член, тип которого является class A
В Scala или Kotlin вы можете определить классы в любом порядке, не опасаясь в этом случае, потому что первый-определенный класс может использовать второй класс, как обычно, даже в случае/класса данных.
Однако в Python следующий код
class A:
b = B()
class B:
a = A()
выдает ошибку компиляции, потому что class B
не определяется, когда определяется class A
Вы можете обойти этот простой случай, как в этом ответе
class A:
pass
class B:
a = A()
A.b = B()
Однако этот способ не работает для классов данных в Python, поскольку назначение членов после определения классов данных не будет обновлять автоматически генерируемые методы классов данных, что делает использование класса данных "бесполезным".
@dataclass
class A:
b: B # or 'b: Optional[B]'
@dataclass
class B:
a: A # or 'a: Optional[A]'
Как я могу избежать этой проблемы?
Ответы
Ответ 1
Существует несколько способов решения круговых зависимостей, подобных этому, см. Подсказки типа: решение круговой зависимости
Вы всегда можете применить декоратор вручную (и обновить аннотации), как показывает ответ @Nearoo.
Однако было бы проще "переслать объявление" в класс:
class A:
pass
@dataclass
class B:
a: A
@dataclass
class A:
b: B
Или просто используйте прямую ссылку:
@dataclass
class B:
a: 'A'
@dataclass
class A:
b: B
Самое чистое - импортировать поведение Python 4.0 (если можно):
from __future__ import annotations
@dataclass
class B:
a: A
@dataclass
class A:
b: B
Ответ 2
Вы можете достичь своей цели, применяя декоратор dataclass
только после того, как мы ввели поле b
в A
Для этого нам просто нужно добавить аннотацию типа в A
__annotations__
-field
Следующий код решает вашу проблему:
class A:
b: None # Note: __annotations__ only exists if >=1 annotation exists
@dataclass
class B:
a: A
A.__annotations__.update(b=B) # Note: not the same as A.b: B
A = dataclass(A) # apply decorator
Что касается безопасности и обоснованности этого метода, PEP 524 заявляет, что
.. на уровне модуля или класса, если элемент, аннотированный, является простым именем, то он и аннотация будут сохранены в атрибуте __annotations__ этого модуля или класса. [Этот атрибут] доступен для записи, поэтому это разрешено:
__annotations__['s'] = str
Поэтому добавление аннотации типа позже путем редактирования __annotations__
идентично определению его при определении класса.
Ответ 3
Поскольку python - это язык скриптов - нет способа сделать это с помощью @dataclass
. Потому что в python нет механизма "автоподвешенного" (зависимого впрыска). В этот момент, если вам нужна круговая зависимость, вы должны использовать один из классов как обычный.
class A:
b = None
@dataclass
class B:
a: A
a = A()
a.b = B(a)
Компилятор Python проходит через каждую строку, не перескакивая с определения класса/функции. И когда компилятор/интерпретатор видит следующую строку b: B
и раньше не видел класс B
- он будет генерировать исключение. NameError: name 'B' is not defined
Хотелось бы верить, что есть способ сделать это (круговая зависимость для @dataclass
), но правда жестока. (Есть много вещей, которые вы можете сделать на Java/другом языке и не можете делать в python. Другое направление этого утверждения правдиво.)