Как определить циклически зависимые классы данных в Python 3.7+? - PullRequest
0 голосов
/ 06 октября 2018

Предположим, что class A имеет члена с типом class B, а class B имеет члена с типом class A.

В Scala или Kotlin вы можете определять классы в любомв этом случае не беспокойтесь, потому что первый определенный класс может использовать второй определенный класс, как обычно, даже в случае case / data.

Однако в 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]`

Как мне избежать этой проблемы?

Ответы [ 3 ]

0 голосов
/ 08 октября 2018

Вы можете достичь своей цели, применив декоратор dataclass только после того, как мы введем поле b в A.Для этого нам просто нужно добавить аннотацию типа в поле __annotations__ *1005*

. Следующий код решает вашу проблему:

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__ идентично определению его в классеопределение.

0 голосов
/ 08 октября 2018

Существует несколько способов решения круговых зависимостей, например, см. Подсказки по типу: решить круговую зависимость

Вы всегда можете применить декоратор вручную (и обновить аннотации), например @ Nearoo'sответ показывает.

Однако, может быть проще "объявить вперед" класс:

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
0 голосов
/ 08 октября 2018

Так как python является языком сценариев - нет способа сделать это с @dataclass.Потому что в python нет механизма "autowired" (внедрение зависимостей).В данный момент, если вам нужна циклическая зависимость - вы должны использовать один из классов как обычный.

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. Другое направление этого утверждения также правдиво.)

...