Search code examples
pythonmodulepython-importpython-3.7python-dataclasses

How to allow relative circular imports in Python3.5+ used only for dataclass type checks?


I have a file structure as follows:

├── test_package
│   ├── __init__.py
│   └── models
│       ├── __init__.py
│       ├── a.py
│       └── b.py

And the following file contents:

# a.py
from .b import B
from dataclasses import dataclass

@dataclass
class A:
    b: B

# b.py
from .a import A
from dataclasses import dataclass

@dataclass
class B:
    a: A

When trying to import package a, I get the following error.

: import test_package.models.a                                                                 
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-e8165f4eb507> in <module>
----> 1 import test_package.models.a

/.../test_package/models/a.py in <module>
----> 1 from .b import B
      2 from dataclasses import dataclass
      3 
      4 @dataclass
      5 class A:

/.../test_package/models/b.py in <module>
----> 1 from .a import A
      2 from dataclasses import dataclass
      3 
      4 @dataclass
      5 class B:

ImportError: cannot import name 'A' from 'test_package.models.a'

According to the Python3.5 change log, "Circular imports involving relative imports are now supported. (Contributed by Brett Cannon and Antoine Pitrou in bpo-17636.)" How do I make this relative circular import work?

Note that I only have to make these imports to do type checking.

Edit:

As a reply to @juanpa.arrivillaga in the comments, here is an example of the same error happening with Python 2.7. It seems like the source files are being found and being executed unless I'm misinterpreting something.

$ cat a.py
from b import b_func

def a_func():
    return "Hi"

$ cat b.py
from a import b_func

def b_func():
    return "Hi"

>>> import a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "a.py", line 1, in <module>
    from b import b_func
  File "b.py", line 1, in <module>
    from a import b_func
ImportError: cannot import name b_func

Solution

  • "When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later." For example,

    @dataclass 
    class A: 
        b: 'B' 
    
    @dataclass 
    class B: 
        a: A
    
    >>> A.__annotations__ # This is what the type checker uses
    {'b': 'B'} 
    

    See https://www.python.org/dev/peps/pep-0484/#forward-references for more information.

    Note this isn't the answer to the question I asked but it is the answer I wished I had received. And so I am posting this answer in case someone goes through the same path that I did.