Remove python cyclic import

user.py:

from story import Story class User: ... def get_stories(self): story_ids = [select from database] return [Story.get_by_id(id) for id in story_ids] 

story.py

 from user import User class Story: ... def __init__(self, id, user_id, content): self.id = id self.user = User.get_by_id(user_id) self.content = content 

as you can see, there is a circular import in this program, which raises ImportError . I found out that I can move the import statement in the method definition to prevent this error. But I still want to know if there is a way to remove circular imports in this case or, if necessary (for good design)?

+8
python design import circular-dependency
source share
4 answers

Another way to reduce the circuit is to change the import style. Change from story import Story to import story , then refer to the class as story.Story . Since you are referring only to the class inside the method, it will not need to access the class until the method is called, after which the import will be completed successfully. (You may need to make this change in one or both modules, depending on which one is imported first.)

However, the design looks a little strange. Your design is such that the User and Story classes are very tightly coupled - none of them can be used without the other. In this case, it would be more reasonable to have them in one module.

+1
source share

The most obvious solution in this case is to completely break the dependency with the User class by changing the interface, so that the Story constructor accepts the actual User , not a user_id . This also leads to a more efficient design: for example, if the user has many stories, the same object can be provided to all of these constructors.

In addition, importing the whole module (that is, Story and User instead of members) should work - first, the imported module will be empty at the time of importing the second; however, this does not matter, since the contents of these modules are not used in the global scope.

This is slightly preferable for importing within the method. Importing inside a method has significant overhead only based on the global search module ( story.Story ), since this is necessary for each method call; in the simple case, it seems that the overhead is at least 30 times.

+1
source share

There are tons of these python circular import questions on the net. I decided to contribute to this thread, because the request contains Ray Hettinger's comment, which legitimizes the use of circular imports, but recommends a solution that, in my opinion, is not a particularly good practice - moving imports to the method.

In addition to the authority of Hettinger, three rejections of common objections are necessary:

  • I have never programmed in Java. I am not trying to use the Java style.
  • Refactoring is not always useful or effective. The logical API sometimes dictates a structure that makes recursive import links inevitable. Remember that code exists for users, not programmers.
  • Combining very large modules can cause readability and maintainability problems, which can be much worse than one or two recursive imports.

In addition, I believe that maintainability and readability dictate that import is grouped at the top of the file, occurs only once for each required name, and that the style from module import name preferable (except, perhaps, for very short module names with many functions e.g. gtk ) because it avoids repetitive verbal interference and makes explicit dependencies dependent.

With this in mind, I will describe a simplified version of my own use case that brought me here and provided my solution.

I have two modules, each of which defines many classes. surface defines geometric surfaces such as planes, spheres, hyperboloids, etc. path defines flat geometric shapes such as lines, hyperbole circles, etc. Logically, these are different categories, and refactoring is not an option in terms of API requirements. However, these two categories are close.

A useful operation intersects two surfaces, for example, the intersection of two planes is a line, or the intersection of a plane and a sphere is a circle.

If, for example, in surface.py you are doing the direct import necessary to implement the return value for the intersection operation:

 from path import Line 

You get:

 Traceback (most recent call last): File "surface.py", line 62, in <module> from path import Line File ".../path.py", line 25, in <module> from surface import Plane File ".../surface.py", line 62, in <module> from path import Line ImportError: cannot import name Line 

Geometrically, planes are used to define paths; in the end, they can be arbitrarily oriented in three (or more) dimensions. Tracing tells you everything that happens and the decision.

Just replace the import statement in surface.py with:

 try: from path import Line except ImportError: pass # skip circular import second pass 

The sequence of operations in the back trace is still happening. Just the second time we ignore import failure. This does not matter, because Line not used at the module level. Therefore, the required surface namespace is loaded into path . Therefore, the parsing of the path namespace can be completed, which allows you to load it into the surface , completing the first acquaintance with from path import Line . Thus, the parsing of the surface namespace can go on and on and on, continuing with whatever it may need.

This is a simple and clear idiom. The syntax is try: ... except ... clearly and succinctly documents the problem of cyclic import, facilitating any future maintenance. Use it whenever a refactor is a really bad idea.

+1
source share

As BrenBarn said, the most obvious solution is to keep User and Story in the same module, which makes sense if the user needs to know anything about Story. Now, if you really need to have them in separate modules, you can also monkeypatch User in story.py add the get_stories method. It is read / decouple accessibility ...

0
source share

All Articles