Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting circular imports

Tags:

I'm working with a project that contains about 30 unique modules. It wasn't designed too well, so it's common that I create circular imports when adding some new functionality to the project.

Of course, when I add the circular import, I'm unaware of it. Sometimes it's pretty obvious I've made a circular import when I get an error like AttributeError: 'module' object has no attribute 'attribute' where I clearly defined 'attribute'. But other times, the code doesn't throw exceptions because of the way it's used.

So, to my question:

Is it possible to programmatically detect when and where a circular import is occuring?

The only solution I can think of so far is to have a module importTracking that contains a dict importingModules, a function importInProgress(file), which increments importingModules[file], and throws an error if it's greater than 1, and a function importComplete(file) which decrements importingModules[file]. All other modules would look like:

import importTracking importTracking.importInProgress(__file__) #module code goes here. importTracking.importComplete(__file__) 

But that looks really nasty, there's got to be a better way to do it, right?

like image 622
Ponkadoodle Avatar asked Mar 09 '10 01:03

Ponkadoodle


People also ask

What are circular imports?

Generally, the Python Circular Import problem occurs when you accidentally name your working file the same as the module name and those modules depend on each other. This way the python opens the same file which causes a circular loop and eventually throws an error.

How can you prevent most likely due to circular imports?

You can, however, use the imported module inside functions and code blocks that don't get run on import. Generally, in most valid cases of circular dependencies, it's possible to refactor or reorganize the code to prevent these errors and move module references inside a code block.

How do you find circular dependency?

By running a cli command npx madge --circular --extensions ts ./ we can quickly get a list of circular dependencies of all . ts files in current directory and its subdirectories.


1 Answers

To avoid having to alter every module, you could stick your import-tracking functionality in a import hook, or in a customized __import__ you could stick in the built-ins -- the latter, for once, might work better, because __import__ gets called even if the module getting imported is already in sys.modules, which is the case during circular imports.

For the implementation I'd simply use a set of the modules "in the process of being imported", something like (benjaoming edit: Inserting a working snippet derived from original):

beingimported = set() originalimport = __import__ def newimport(modulename, *args, **kwargs):     if modulename in beingimported:         print "Importing in circles", modulename, args, kwargs         print "    Import stack trace -> ", beingimported         # sys.exit(1) # Normally exiting is a bad idea.     beingimported.add(modulename)     result = originalimport(modulename, *args, **kwargs)     if modulename in beingimported:         beingimported.remove(modulename)     return result import __builtin__ __builtin__.__import__ = newimport 
like image 188
Alex Martelli Avatar answered Oct 10 '22 22:10

Alex Martelli