If you do import foo (inside bar.py) and import bar (inside foo.py), it will work fine. By the time anything actually runs, both modules will be fully loaded and will have references to each other.
The problem is when instead you do from foo import abc (inside bar.py) and from bar import xyz (inside foo.py). Because now each module requires the other module to already be imported (so that the name we are importing exists) before it can be imported.
Examples of working circular imports in Python 2 and Python 3
The article When are Python circular imports fatal? gives four examples when circular imports are, for the reason explained above, nonfatal.
Top of module; no from; Python 2 only
# lib/foo.py # lib/bar.py
import bar import foo
def abc(): def xyz():
print(bar.xyz.__name__) print(foo.abc.__name__)
Top of module; from ok; relative ok; Python 3 only
# lib/foo.py # lib/bar.py
from . import bar from . import foo
def abc(): def xyz():
print(bar.xyz.__name__) print(abc.__name__)
Top of module; no from; no relative
# lib/foo.py # lib/bar.py
import lib.bar import lib.foo
def abc(): def xyz():
print(lib.bar.xyz.__name__) print(iib.foo.abc.__name__)
Bottom of module; import attribute, not module; from okay
# lib/foo.py # lib/bar.py
def abc(): def xyz():
print(xyz.__name__) print(abc.__name__)
from .bar import xyz from .foo import abc
Top of function; from okay
# lib/foo.py # lib/bar.py
def abc(): def xyz():
from . import bar from . import foo
print(bar.xyz.__name__) print(foo.abc.__name__)
Additional examples
The article cited above does not discuss star imports.