I have a scenario where I'm working with two classes, A and B, located in separate modules a.py
and b.py
. Class A uses class B as a type hint, and class B uses class A in its functionality (Association relationship). Currently, my code achieves this functionality, but I'm open to suggestions for improving or simplifying the implementation due to concerns about circular dependencies and maintainability. How can I design this interaction between classes A and B in a cleaner and more organized way while still using type hints effectively?
I currently handle this problem by something like this:
a.py:
from b import B
class A:
def __init__(self, test: B) -> None:
self.test = test
def process(self):
return self.test.hi()
b.py:
class B:
def __init__(self) -> None:
print(1)
def hi(self):
print("hi")
def import_A(self):
from a import A
t = A(self)
print(t.process())
Note that if I move from a import A to the top, a circular import error occurs. Is there any way to import everything at the beginning without causing any error?
A few suggestions
def import_A(self):
from a import A
t = A(self)
print(t.process())
this logic doesn't really have anything to do with B
, and could be written in another file.
from b import B
from a import A
When you import a specific value from a file, it forces python to resolve that immediately. If you instead say
import x
...
foobar(x.X)
then you have a chance of allowing some "circular" imports surviving. Try to avoid using this excessively, it is still a code smell and likely means something isn't quite right in the design.
If two classes are really intertwined, it can often be much more preferable to put them together than apart, even if it makes some files rather large. Consider Tree
and Leaf
node classes that have different implementations but definitely should reside together.
Pulling abstractions into common files that both can import from can often help remove issues, this can be combined with "put them in the same file" to have abstractions together and concrete implementations separate.
Likewise, putting the execution or test portions in a separate file means that you also avoid loops in the full logic.
a --\ c a b c
\-- b vs / \ vs \ / vs / \
a b e a b
\ /
e