qkhhly
qkhhly

Reputation: 1220

relative import when using imp, __init__.py behaves differently

I am using python 3.7. When using imp, I noticed relative imports work differently depending on where the relative import happens.

For example, I have the following directory:

.
├── __init__.py
└── dir2
    ├── __init__.py
    ├── a.py
    └── b.py

and file content as follows:

~/personal/dir1 via 🐍 v3.7.7 via C py372
❯ cat dir2/__init__.py
from .a import default
print("default is", default)

~/personal/dir1 via 🐍 v3.7.7 via C py372
❯ cat dir2/a.py

default = 12

~/personal/dir1 via 🐍 v3.7.7 via C py372
❯ cat dir2/b.py
from .a import default
print("default is", default)

~/personal/dir1 via 🐍 v3.7.7 via C py372

And I use imp.load_source for both dir2/__init__.py and dir2/b.py, I got the following results:

❯ ipython
Python 3.7.7 (default, May  6 2020, 04:59:01)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.16.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import imp
/Users/user/anaconda3/envs/py372/bin/ipython:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  #!/Users/user/anaconda3/envs/py372/bin/python

In [2]: imp.load_source("m", "dir2/__init__.py")
default is 12
Out[2]: <module 'm' from 'dir2/__init__.py'>

In [3]: imp.load_source("m", "dir2/b.py")
---------------------------------------------------------
ImportError             Traceback (most recent call last)
<ipython-input-3-40c4f63c0bb1> in <module>
----> 1 imp.load_source("m", "dir2/b.py")

~/anaconda3/envs/py372/lib/python3.7/imp.py in load_source(name, pathname, file)
    167     spec = util.spec_from_file_location(name, pathname, loader=loader)
    168     if name in sys.modules:
--> 169         module = _exec(spec, sys.modules[name])
    170     else:
    171         module = _load(spec)

~/anaconda3/envs/py372/lib/python3.7/importlib/_bootstrap.py in _exec(spec, module)

~/anaconda3/envs/py372/lib/python3.7/importlib/_bootstrap_external.py in exec_module(self, module)

~/anaconda3/envs/py372/lib/python3.7/importlib/_bootstrap.py in _call_with_frames_removed(f, *args, **kwds)

~/personal/dir1/dir2/b.py in <module>
----> 1 from .a import default
      2 print("default is", default)

ImportError: attempted relative import with no known parent package

Why imp.load_source behaves differently for __init__.py versus other python file?

Upvotes: 0

Views: 415

Answers (1)

user2357112
user2357112

Reputation: 280227

Relative imports are not a directory traversal mechanism. They are for importing other contents of the package in which they appear.

When you call imp.load_source("m", "dir2/__init__.py"), Python thinks you're trying to load a package named m whose __init__.py is dir2/__init__.py. In this context, relative imports are relative to the package m, and they search for submodules in directory dir2.

When you call imp.load_source("m", "dir2/b.py"), Python thinks you're trying to load a top-level module named m. There is no package for relative imports to be relative to, and relative imports are meaningless in this context.

Upvotes: 1

Related Questions