Avoiding Circular Imports
00:00 In the previous lesson, I introduced you to the module cache. In this lesson, I’ll show you when Python does and when it doesn’t deal with circular imports.
00:09 You’ve seen how one module can import another, but what happens if that second module also imports the first? This is called a circular import, and, in fact, it can be much more complicated than A imports B and B imports A, it could be A, B, C, D, E, F, A.
00:28 Don’t panic though. Since modules get cached, this isn’t a big deal for the A, B, A situation. When B imports A, it gets the cached copy of A that has already been imported.
00:39 There is a caveat though. If A changes itself in a way that B is dependent upon after B attempts to import A, you might have a problem.
00:50 Let’s look at some code to see what this means and what you can do about it.
00:55
In my top window, I have a short program called black.py
. It has an import between two print()
statements. Nothing tricky so far.
01:04
Where it gets a little quirky is in the middle window. white.py
also has an import. It imports black.py
. When I import black
, it imports white
, which then loads black
.
01:18
Since the module cache already has black
in it, this isn’t a problem. Let’s actually see this work. See no issue. One thing to note though, which hints at possible trouble is the order of the print()
statements.
01:33
Since black
imports white
between two print()
statements, the Goodbye from
black` doesn’t get run until white
has been fully imported.
01:41
Since the importing of white
has the side effect of running its two print()
statements, the prints from black
sandwich the prints from white
.
01:49
Pause here and think for a second. Because of this sandwiching, there’s a potential problem. If black
does something after importing white
that white
is dependent upon, it won’t be done when the white
import happens.
02:02
Let me show you just what I mean, going from black
and white
to up
and down
. Like before up
imports down
, and this time it does something additional.
02:13
It uses the count
value from down
and similar to before down
imports up
. And here’s the problem. It defines count
after the circular import.
02:25
Because of that sandwiching behavior you saw before, you now have a situation where down
doesn’t define the count
value until after importing up
and importing up
is supposed to try to use that same value.
02:38 Let’s see what Python does with this.
02:44
Once more, you see the beauty of the new error messages in more recent versions of Python. This used to just be an AttributeError
saying there was no count
, which of course is confusing because it’s right there.
02:56 The newer message includes the hint that you might have a circular import. What can you do about this? Unfortunately, the short answer is just don’t do it in the first place.
03:05 Circular imports are usually a result of a jumbled design, and if you’re finding you’ve got one, you should probably rethink your code structure. Sometimes though, they’re unavoidable.
03:15 So let’s look at one way to get around the problem.
03:20
First it was black
and white
, then it was up
and down
, now it’s left
and right
. left
as a count
, and it imports right
.
03:30
It then calls a function from right
,
03:33 and this is the solution to the circular import. By moving the import inside a function, the import does not actually get called until the function gets invoked.
03:44
Remember, when loading a module, any bare code gets run as a side effect, but the get_count()
only gets defined, not executed. This allows the importation of right
to complete before the circular import occurs.
03:57 By doing this, you’re back to the module cache taking care of the problem.
04:02
Let me prove that this works, and there you go. Notice how the print()
statements from right
get called before the total is printed out.
04:12
That’s because the module is fully loaded as importing left
doesn’t occur until afterwards.
04:18 In short, Python is actually better about circular imports than many other languages, and that’s because of the module cache and the dynamic nature of the interpreter.
04:27 But just because it handles it in most situations doesn’t mean you should use it. 99% of the time, circular imports can be solved using better design.
04:38 Speaking of the dynamic nature of Python, next up I’ll show you how to dynamically load modules and resources.
Become a Member to join the conversation.