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
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.