Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

isinstance fails for a type imported via package and from the same module directly

/Project
|-- main.py
|--/lib
|  |--__init__.py
|  |--foo.py
|  |--Types.py

/Project/lib has been added to the PYTHONPATH variables.

Types.py:

class Custom(object):
    def __init__(self):
        a = 1
        b = 2

foo.py:

from Types import Custom
def foo(o):
    assert isinstance(o, Custom)

Finally, from main.py:

from lib.Types import Custom
from lib.foo import foo
a = Custom()
foo(a)

The problem now is, that a is of type lib.foo.Custom, while the isinstance call will check if it equals foo.Custom, which obviously returns false.

How can I avoid this problem, without having to change anything in the library (lib)?

like image 202
netik Avatar asked Oct 12 '17 11:10

netik


2 Answers

You should not both make lib a package and add it to PYTHONPATH. This makes it possible to import its modules both as lib. and directly, setting yourself up for failure.

As you can see,

lib.Types.Custom != Types.Custom

because of the way Python imports work.

Python searches the import path and parses an appropriate entry that it finds.

  • When you import lib.Types, it imports the lib directory as a package, then lib/Types.py as a submodule inside it, creating module objects lib and lib.Types in sys.modules.
  • When you import Types, it imports Types.py as a standalone module, creating a module object Types in sys.modules.

So, Types and lib.Types end up as two different module objects. Python doesn't check if they are the same file to keep things simple and to avoid second-guessing you.

(This is actually listed in the Traps for the Unwary in Python’s Import System article as the "double import trap".)


If you remove lib from PYTHONPATH, the import in lib/foo.py would need to become a relative import:

from .Types import Custom

or an absolute import:

from lib.Types import Custom
like image 84
ivan_pozdeev Avatar answered Sep 18 '22 21:09

ivan_pozdeev


When a module is imported thru two different path in the same process - like here with import Types in foo.py and import lib.Types in main.py, it is really imported twice, yielding two distinct module objects, each with it's own distinct functions and class instances (you can check by yourself using id(obj_or_class)), effectively breaking is and isinstance tests.

The solution here would be to add Project (not Project/lib) to your pythonpath (fwiw that's what should have been done anyway - pythonpath/sys.path should be a list of directories containing packages and modules, not the packages directories themselves) and use from lib.Type import Custom everywhere, so you only have one single instance of the module.

like image 42
bruno desthuilliers Avatar answered Sep 20 '22 21:09

bruno desthuilliers