93

I have two files, node.py and path.py, which define two classes, Node and Path, respectively.

Up to today, the definition for Path referenced the Node object, and therefore I had done

from node.py import *

in the path.py file.

However, as of today I created a new method for Node that references the Path object.

I had problems when trying to import path.py: I tried it, and when the program ran and called the Path method that uses Node, an exception rose about Node not being defined.

What do I do?

Ram Rachum
  • 84,019
  • 84
  • 236
  • 374
  • 2
    duplicate? http://stackoverflow.com/questions/744373/python-cyclic-imports – Paolo Bergantino May 21 '09 at 20:11
  • 3
    Are you trying to have one class per file? This is why that rarely works out well. – S.Lott May 21 '09 at 20:26
  • 4
    Agree with S.Lott. Python is not Java. You don't need one class per file. – Daniel Roseman May 21 '09 at 21:34
  • http://effbot.org/zone/import-confusion.htm – PersianGulf Sep 17 '13 at 00:17
  • 75
    A couple of people have said "you don't need one class per file" and words to the effect "don't try to be Java". OK - but it's off the point. Class definitions can get very large and bundling them into the same file can make for a very large, unreadable file. In a program I am working on with 8 mutually dependent classes, each of which is several hundred lines in length, I see no benefit in keeping them in the same file and a considerable benefit in keeping them separate. – sfkleach May 28 '16 at 09:08
  • 1
    also just as a reference, it seems circular imports are allowed on python 3.5 (and probably beyond) but not 3.4 (and probably bellow). – Charlie Parker Feb 08 '17 at 15:17
  • 6
    Could not upvote @sfkleach enough.. As if 1,000+ LOC in a multi-class file isn't bad enough to read or maintain, the test file (people *do* write tests for Python classes, right?) is going to be *much* longer and even more of a maintenance nightmare. Just because Python sometimes makes good organization difficult doesn't mean we should abandon maintainable code. – kevlarr Jan 24 '19 at 16:16

5 Answers5

131

Importing Python Modules is a great article that explains circular imports in Python.

The easiest way to fix this is to move the path import to the end of the node module.

Jay
  • 510
  • 6
  • 17
Nadia Alramli
  • 111,714
  • 37
  • 173
  • 152
  • 1
    Okay, but the thing is, I have two other modules `tree.py` and `block.py` in that package that require `node.py` and are required by `path.py`. So am I supposed to put them all in one file? I liked having one module per class. – Ram Rachum May 21 '09 at 20:23
  • 3
    Have you tried my suggestion? It'll probably work. Just move the import to the end of the file. I advice you to read the article to understand why this is happening. – Nadia Alramli May 21 '09 at 20:27
  • 12
    By the way, is there some reason other than you "liking" it that you want one class per module? The few times I've seen this preference, it was because it's Java-like. – tzot May 22 '09 at 00:59
  • 4
    the link to Importing Python Modules is broken. – Ben2209 Dec 09 '20 at 18:16
  • 1
    The newest archivation of the article is: https://web.archive.org/web/20200917011425/https://effbot.org/zone/import-confusion.htm after that the web became nonfunctional. Unfortunately the latest update date is still 2001-02-02 which makes the article very old. E.g. it is not recommended to use `__import__()` for normal tasks. See https://docs.python.org/3/library/functions.html#__import__ – pabouk - Ukraine stay strong Sep 27 '21 at 09:35
  • @tzot Each class could be hundreds or thousands of lines long. You might also have several classes inheriting from the one base class, in which case having multiple slight variations of the same thing in the one file becomes troublesome to navigate. Splitting things out also makes it easier for teams to work in parallel and easier to test smaller, individual modules. It's not a java specific issue. – Sicklebrick Apr 01 '22 at 12:00
32

One other approach is importing one of the two modules only in the function where you need it in the other. Sure, this works best if you only need it in one or a small number of functions:

# in node.py 
from path import Path
class Node 
    ...

# in path.py
class Path
  def method_needs_node(): 
    from node import Node
    n = Node()
    ...
mircealungu
  • 6,831
  • 7
  • 34
  • 44
6

You may not need to import Path in node.py in order for Path and Node to make use of one another.

# in __init__.py  (The order of imports should not matter.)
from .node import Node
from .path import Path

# in path.py 
from . import Node
class Path
  ...

  def return_something_pathy(self): 
    ...

# in node.py
class Node
  def __init__(self, path): 
    self.path = path
    ...

  def a_node_method():
    print(self.path.return_something_pathy())

To make it clear that Node is making use of Path, add type hinting. There is a feature available starting with Python 3.7 to support forward references in type annotations, described in PEP 563.

# in node.py  (Now with type hinting.)
from __future__ import annotations

class Node
  def __init__(self, path: Path): 
    self.path = path
    ...

  def a_node_method():
    print(self.path.return_something_pathy())

I came across a Yet another solution to dig you out of a circular import hole in Python is a great blog post which taught me this.

Bryan Roach
  • 743
  • 9
  • 12
  • By itself, a forward reference in type annotation does NOT work in case of **absent** import in `node.py`. For make annotations work you still need `from .path import Path`, but you could place it under `if typing.TYPE_CHECKING:` branch. With such branch both real execution and linter would work. – Tsyvarev Jul 08 '22 at 10:57
5

I prefer to break a circular dependency by declaring one of the dependencies in the constructor of the other dependent class. In my view this keeps the code neater, and gives easy access to all methods who require the dependency.

So in my case I have a CustomerService and a UserService who depend on each other. I break the circular dependency as follows:

class UserService:

    def __init__(self):
        # Declared in constructor to avoid circular dependency
        from server.portal.services.admin.customer_service import CustomerService
        self.customer_service = CustomerService()

    def create_user(self, customer_id: int) -> User:
        # Now easy to access the dependency from any method
        customer = self.customer_service.get_by_id(customer_id)
Iain Hunter
  • 4,319
  • 1
  • 27
  • 13
  • Here, the problem stays when it originates from one class.inheritance, and can be solved with multiple class inheritance. – Flint Mar 01 '23 at 15:40
1

Another method is to define them both in the same module, and to delay defining the types. A little like this:

class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...


class Path:
    def method_needs_node(self):
       n = Node()

Node._path_type = Path

It may be nicer to be symmetrical about this:

class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...


class Path:
    _node_type: type = None

    def method_needs_node(self):
       n = Node()

Node._path_type = Path
Path._node_type = Node

This could also be done in multiple modules:

# in node.py
class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...

# in path.py
from .node import Node

class Path:
    _node_type: type = None

    def method_needs_node(self):
       n = self._node_type()

Node._path_type = Path
Path._node_type = Node

# in __init__.py (note that order is important now)
from .node import Node
from .path import Path
Richard Vodden
  • 318
  • 3
  • 12