196

I know the issue of circular imports in python has come up many times before and I have read these discussions. The comment that is made repeatedly in these discussions is that a circular import is a sign of a bad design and the code should be reorganised to avoid the circular import.

Could someone tell me how to avoid a circular import in this situation?: I have two classes and I want each class to have a constructor (method) which takes an instance of the other class and returns an instance of the class.

More specifically, one class is mutable and one is immutable. The immutable class is needed for hashing, comparing and so on. The mutable class is needed to do things too. This is similar to sets and frozensets or to lists and tuples.

I could put both class definitions in the same module. Are there any other suggestions?

A toy example would be class A which has an attribute which is a list and class B which has an attribute which is a tuple. Then class A has a method which takes an instance of class B and returns an instance of class A (by converting the tuple to a list) and similarly class B has a method which takes an instance of class A and returns an instance of class B (by converting the list to a tuple).

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
BWW
  • 2,071
  • 2
  • 13
  • 5

3 Answers3

364

Consider the following example python package where a.py and b.py depend on each other:

/package
    __init__.py
    a.py
    b.py

Types of circular import problems

Circular import dependencies typically fall into two categories depending on what you're trying to import and where you're using it inside each module. (And whether you're using python 2 or 3).

1. Errors importing modules with circular imports

In some cases, just importing a module with a circular import dependency can result in errors even if you're not referencing anything from the imported module.

There are several standard ways to import a module in python

import package.a           # (1) Absolute import
import package.a as a_mod  # (2) Absolute import bound to different name
from package import a      # (3) Alternate absolute import
import a                   # (4) Implicit relative import (deprecated, python 2 only)
from . import a            # (5) Explicit relative import

Unfortunately, only the 1st and 4th options actually work when you have circular dependencies (the rest all raise ImportError or AttributeError). In general, you shouldn't be using the 4th syntax, since it only works in python2 and runs the risk of clashing with other 3rd party modules. So really, only the first syntax is guaranteed to work.

EDIT: The ImportError and AttributeError issues only occur in python 2. In python 3 the import machinery has been rewritten and all of these import statements (with the exception of 4) will work, even with circular dependencies. While the solutions in this section may help refactoring python 3 code, they are mainly intended for people using python 2.

Absolute Import

Just use the first import syntax above. The downside to this method is that the import names can get super long for large packages.

In a.py

import package.b

In b.py

import package.a

Defer import until later

I've seen this method used in lots of packages, but it still feels hacky to me, and I dislike that I can't look at the top of a module and see all its dependencies, I have to go searching through all the functions as well.

In a.py

def func():
    from package import b

In b.py

def func():
    from package import a

Put all imports in a central module

This also works, but has the same problem as the first method, where all the package and submodule calls get super long. It also has two major flaws -- it forces all the submodules to be imported, even if you're only using one or two, and you still can't look at any of the submodules and quickly see their dependencies at the top, you have to go sifting through functions.

In __init__.py

from . import a
from . import b

In a.py

import package

def func():
    package.b.some_object()

In b.py

import package

def func():
    package.a.some_object()

2. Errors using imported objects with circular dependencies

Now, while you may be able to import a module with a circular import dependency, you won't be able to import any objects defined in the module or actually be able to reference that imported module anywhere in the top level of the module where you're importing it. You can, however, use the imported module inside functions and code blocks that don't get run on import.

For example, this will work:

package/a.py

import package.b

def func_a():
    return "a"

package/b.py

import package.a

def func_b():
    # Notice how package.a is only referenced *inside* a function
    # and not the top level of the module.
    return package.a.func_a() + "b"

But this won't work

package/a.py

import package.b

class A(object):
    pass

package/b.py

import package.a

# package.a is referenced at the top level of the module
class B(package.a.A):
    pass

You'll get an exception

AttributeError: module 'package' has no attribute 'a'

Generally, in most valid cases of circular dependencies, it's possible to refactor or reorganize the code to prevent these errors and move module references inside a code block.

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • 17
    Brendan, this is an amazingly through answer! I have read a hundred answers about solving circular imports, finally I get it. thx! By the way, you might want to add one more solution to your list: set global for each package to None at top of file, then inject module into global at runtime. This has advantage that all module names are at top of file. – Dan Oblinger May 28 '16 at 15:51
  • 1
    You can somewhat combine the first and second approaches by using absolute imports (so you can see all the dependencies at the top) and naming the components you want at point-of-use. Something like import package;def func(): a = package.a in lieu of from package import a – Alex H. Jul 28 '16 at 18:34
  • 2
    Would deferring import until inside a function also be *slower*, in case the function is repeatedly called? Or does the import only occur once? – Hassan Baig Oct 12 '16 at 08:22
  • @HassanBaig Slightly slower perhaps. It will add additional bytecode statements, which theoretically adds more computational time. It won't actually reload the import on each function call though, just the first time it's called. – Brendan Abel Oct 18 '16 at 22:51
  • 6
    I'm curious about the statement that this has been fixed in Python 3. Do you have a good reference describing the way that this has changed? I know that there have been changes in that area between 2 and 3, but a [small example](https://gist.github.com/robhague/d1417cb764c05dd8d3c9500c9fea0d92) of the `from...import` form fails in the same way on both Python 2.7.11 and Python 3.5.1. – Rob Hague Apr 05 '17 at 09:57
  • 1
    @RobHague in your example, your trying to Import symbols from within the file. The part that was fixed is that you can use relative and "from" imports to import modules (the modules themselves, not necessarily symbols inside them) from within a package before the `__init__` for that package has finished loading. – Brendan Abel Apr 05 '17 at 13:21
  • @BrendanAbel Thanks for the clarification; that makes a lot of sense in the context of the new handling of package directories. – Rob Hague Apr 10 '17 at 08:32
  • 3
    How about `if 'module_name' not in sys.modules: import ...` ? – jbryanscott May 26 '17 at 22:36
  • @BrendanAbel Options 1, 3, and 5 work for me in Python 3.6, but option 2 fails with `AttributeError: module 'package' has no attribute 'a'`. It seems that this is because `sys.modules['package'].a` is set *after* the code for `package.a` is executed (so the [implied `a = package.a`](https://docs.python.org/3.6/library/functions.html#__import__) fails), but why do 3 and 5 succeed? – Will Vousden Oct 17 '17 at 14:38
  • Option 2 could easily be fixed by replacing `a = package.a` with `a = sys.modules['package.a']` (referring to the `__import__` docs as above). I wonder why they didn't do this! – Will Vousden Oct 17 '17 at 14:55
  • Absolute import bound to different name worked for me in python3.9: `import package.a as a_mod`. Importing into a method worked too. – oz19 Sep 29 '21 at 09:28
  • "Unfortunately, only the 1st and 4th options actually work when you have circular dependencies (the rest all raise ImportError or AttributeError)...." I don't think this section is correct or accurate even with the note about 3.x. Changes were made part-way through: in [3.5](https://github.com/python/cpython/issues/61836) and in [3.7](https://github.com/python/cpython/issues/74210). It is definitely possible to get an `AttributeError` from a circular import if you use an attribute that doesn't exist yet; only the `ImportError`s are prevented. – Karl Knechtel Aug 11 '22 at 03:20
  • The biggest problem I face is I use typing and enums or user defined types- I put enums and user defined types in their own module, then use them on method signatures. It sort of forces me to move the enums/udt into the module where the signature is. But then the run can come that the enum/udt could be required in other modules too. This is the downfall of Python in my eyes. Otherwise I love the language. – JGFMK Nov 15 '22 at 15:26
  • @JGFMK You should be able to just import those Enums and UDT's to use them for typing purposes. For type hints specifically, you can use [Forward References](https://peps.python.org/pep-0484/#forward-references) to avoid circular dependencies – Brendan Abel Nov 15 '22 at 15:53
  • You end up having to do things like convert the types into strings to circumvent the circularity issues. eg.. this is an attribute in an enum... nodlstms: 'Optional[tidal.Nodlstm]' (LOL -exactly what was in the link) – JGFMK Nov 15 '22 at 18:32
  • @JGFMK Yeah, there are ways to avoid using the stringified forward references -- see https://peps.python.org/pep-0563/ and https://peps.python.org/pep-0649/ – Brendan Abel Nov 16 '22 at 05:37
  • Why I get annoyed is I use Python to run conversions. So if the code runs for 40 min, then the code bombs on a circular reference for a newly added piece of code. it is extremely annoying. In Java you'd just catch similar things to this as a compilation error and not waste so much time. – JGFMK Nov 16 '22 at 08:23
162

Only import the module, don't import from the module:

Consider a.py:

import b

class A:
    def bar(self):
        return b.B()

and b.py:

import a

class B:
    def bar(self):
        return a.A()

This works perfectly fine.

rumpel
  • 7,870
  • 2
  • 38
  • 39
  • 3
    Wow!! Thanks, I knew the trick putting one of the "from imports" causing the circular import error at the bottom of the module, but this is better! – Caumons Jul 01 '13 at 14:01
  • 68
    Doesn't seem to work with submodules `import foobar.mod_a` and `import foobar.mod_b` doesn't work like described above. – Nick Oct 02 '13 at 00:39
  • 9
    Also this has a big disadvantage: You end up with run-time errors instead of import-time errors when you e.g. delete a function and forget to update a reference to it somewhere. – ThiefMaster Feb 24 '15 at 14:26
  • 26
    Community: This this answer does not suffice. please read Brenden's below! – Dan Oblinger May 28 '16 at 15:52
  • 1
    @Nick it does, except that you would have to use `foobar.mod_a.function_foo` (assuming `function_foo` exists in `mod_a`). In any case, I am not recommending, just pointing it out. – toto_tico Oct 11 '17 at 15:06
  • This **only** avoids the problem as long as there is **no top-level code** that tries to access an attribute from the imported module. – Karl Knechtel Aug 11 '22 at 03:50
9

We do a combination of absolute imports and functions for better reading and shorter access strings.

  • Advantage: Shorter access strings compared to pure absolute imports
  • Disadvantage: a bit more overhead due to extra function call

main/sub/a.py

import main.sub.b
b_mod = lambda: main.sub.b

class A():
    def __init__(self):
        print('in class "A":', b_mod().B.__name__)

main/sub/b.py

import main.sub.a
a_mod = lambda: main.sub.a

class B():
    def __init__(self):
        print('in class "B":', a_mod().A.__name__)
  • 5
    Why use the `lambda`? Why not just `b_mod = main.sub.b`? – Brendan Abel Feb 13 '17 at 23:26
  • 13
    @BrendanAbel The lambda delays access until when it needs to be used. Without it, an AttributeError is raised – Izkata Feb 17 '17 at 17:26
  • 4
    Please don't assign lambda expressions to bound names. Use a classic `def` instead. – TDk Nov 02 '20 at 10:15
  • 1
    Note that wrapping `a_mod` in a function can have some unexpected effects. For example, `type(a_mod)` always returns ``, regardless of what the type of `main.sub.a` is. Docstrings also won't work as expected. – Zekko Jan 07 '21 at 13:12
  • Why not just alias with `as`? You can import like `import main.sub.b as b_mod` and use the shorter `b_mod`. Similar for the `a_mod` – wow so much info Sep 10 '22 at 22:38