Why do circular imports cause problems with object identity using `isinstance`?

  • A+
Category:Languages

After a few hours of isolating a bug, I have come up with the following MCVE example to demonstrate the problem I've had:

a.py:

from b import get_foo_indirectly  class Foo:     pass  if __name__ == '__main__':     print("Indirect:", isinstance(get_foo_indirectly(), Foo))     print("Direct:", isinstance(Foo(), Foo)) 

b.py:

def get_foo_indirectly():     from a import Foo     return Foo() 

The expected output of a.py is:

Indirect: True Direct: True 

The actual output is:

Indirect: False Direct: True 

Moreover, if I create a separate module c.py, the output is as expected:

from a import Foo from b import get_foo_indirectly  if __name__ == '__main__':     print("Indirect:", isinstance(get_foo_indirectly(), Foo))     print("Direct:", isinstance(Foo(), Foo)) 

Clearly, the interaction between isinstance and the import machinery is not behaving quite like I expected it to. It seems like the use of circular imports has bitten me hard. Why? Is this Python's expected behavior?

Note that this is very oversimplified of the actual context in which I encountered this behavior; modules a and b were both large modules, and b was separated because it had a distinct purpose from a. Now that I've seen the consequences of circular imports, I will probably combine them, perhaps relegating some of the long-winded behavior in b.

 


When you run a Python script it automatically assumes the name __main__. At the time you imported a.py in b.py Python assumed the usual module name (i.e. the name of the file), and at runtime Python changed to __main__ because it's the entry point script; so, it's like the Foo class was declared in two different places: the __main__ module and the a module.

You're then comparing an instance of a.Foo (created inside get_foo_indirectly) and __main__.Foo.

This was already discussed here.

If you need to do circular imports, don't put the entry point script in the loop. This way you avoid this --very consusing-- behaviour of Python.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: