Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

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.