简体   繁体   中英

Python: Circular dependency of dataclasses / Forward variable declaration?

So, I have these two dataclasses in a file:

@dataclass
class A:
    children: List[B]

@dataclass
class B:
    parent: A

, which are possible with the use of the __future__.annotations feature.

Then I have two other files, each with a bunch of objects for each type that are static for my project.

File objects_A :

import objects_B

obj_a1 = A(
    children=[
        objects_B.obj_b1,
        objects_B.obj_b2
    ]
)

File objects_B :

import objects_A

obj_b1 = B(
    parent=objects_A.obj_a1
)

obj_b2 = B(
    parent=objects_A.obj_a1
)

Obviously, there a circular dependency problem between the files, but it wouldn't work even if they were in the same file, as a variable of one type depends on the other to succeed.
Initialising the B objects inside obj_a1 also won't work as there is no concept of self here.

At the moment, I'm setting parent to None (against the type hinting), and then do a loop on obj_a1 to set them up:

for obj_b in obj_a1.children:
    obj_b.parent = obj_a1

Any bright ideas folks?
Don't know if it helps, but these objects are static (they will not change after these declarations) and they have kind of a parent-children relationship (as you surely have noticed).
If possible, I would like to have the variables of each type in different files.

I know that I'm late but I'll just leave my answer here for others to use.

According to PEP 563 , python 3.7 has introduced lazy evaluation of annotations which can be very useful in the case of a circular dependency.

@dataclass
class StudentData:
    school: 'SchoolData'

@dataclass
class SchoolData:
    students: StudentData

As you can see, the SchoolData type annotation is wrapped inside quotations which allows you to reference the SchoolData type before its declaration.

One way to solve this is to use a base class as an alternative to forward declaration.

@dataclass
class ParentBase:
    children: List[object]


@dataclass
class Child:
    parent: ParentBase


@dataclass
class Parent(ParentBase):
    children: List[Child]



parent = Parent(children=[])
child = Child(parent=parent)
parent.children.append(child)

This will work. I have added the children: List[object] in the ParentBase . This is not necessary for this code to work, but if you add it your IDE can help you when you start accessing child.parent. .

This will not stop anyone from just instantiating a Child with a ParentBase . To solve that problem, you could do something like this:

@dataclass
class ParentBase(abc.ABC):
    children: List[object]

    @abc.abstractmethod
    def __post_init__(self):
        pass


@dataclass
class Child:
    parent: ParentBase


@dataclass
class Parent(ParentBase):
    children: List[Child]

    def __post_init__(self):
        pass


parent = Parent(children=[])
child = Child(parent=ParentBase())  # TypeError
child = Child(parent=parent)
parent.children.append(child)

It might be too defensive and too much boilerplate code. It depends on the preference of your team whether you want to make this safer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM