Can anyone explain python's relative imports?

Python

Python Problem Overview


I can't for the life of me get python's relative imports to work. I have created a simple example of where it does not function:

The directory structure is:

__init__.py
start.py
parent.py
sub/
    __init__.py
    relative.py

/start.py contains just: import sub.relative

/sub/relative.py contains just from .. import parent

All other files are blank.

When executing the following on the command line:

$ cd /
$ python start.py

I get:

Traceback (most recent call last):
  File "start.py", line 1, in <module>
    import sub.relative
  File "/home/cvondrick/sandbox/sub/relative.py", line 1, in <module>
    from .. import parent
ValueError: Attempted relative import beyond toplevel package

I am using Python 2.6. Why is this the case? How do I make this sandbox example work?

Python Solutions


Solution 1 - Python

You are importing from package "sub". start.py is not itself in a package even if there is a __init__.py present.

You would need to start your program from one directory over parent.py:

./start.py

./pkg/__init__.py
./pkg/parent.py
./pkg/sub/__init__.py
./pkg/sub/relative.py

With start.py:

import pkg.sub.relative

Now pkg is the top level package and your relative import should work.


If you want to stick with your current layout you can just use import parent. Because you use start.py to launch your interpreter, the directory where start.py is located is in your python path. parent.py lives there as a separate module.

You can also safely delete the top level __init__.py, if you don't import anything into a script further up the directory tree.

Solution 2 - Python

If you are going to call relative.py directly and i.e. if you really want to import from a top level module you have to explicitly add it to the sys.path list.
Here is how it should work:

# Add this line to the beginning of relative.py file
import sys
sys.path.append('..')

# Now you can do imports from one directory top cause it is in the sys.path
import parent

# And even like this:
from parent import Parent

If you think the above can cause some kind of inconsistency you can use this instead:

sys.path.append(sys.path[0] + "/..")

sys.path[0] refers to the path that the entry point was ran from.

Solution 3 - Python

Checking it out in python3:

python -V
Python 3.6.5

Example1:

.
├── parent.py
├── start.py
└── sub
    └── relative.py

- start.py
import sub.relative

- parent.py
print('Hello from parent.py')

- sub/relative.py
from .. import parent

If we run it like this(just to make sure PYTHONPATH is empty):

PYTHONPATH='' python3 start.py

Output:

Traceback (most recent call last):
  File "start.py", line 1, in <module>
    import sub.relative
  File "/python-import-examples/so-example-v1/sub/relative.py", line 1, in <module>
    from .. import parent
ValueError: attempted relative import beyond top-level package

If we change import in sub/relative.py

- sub/relative.py
import parent

If we run it like this:

PYTHONPATH='' python3 start.py

Output:

Hello from parent.py

Example2:

.
├── parent.py
└── sub
    ├── relative.py
    └── start.py

- parent.py
print('Hello from parent.py')

- sub/relative.py
print('Hello from relative.py')

- sub/start.py
import relative
from .. import parent

Run it like:

PYTHONPATH='' python3 sub/start.py

Output:

Hello from relative.py
Traceback (most recent call last):
  File "sub/start.py", line 2, in <module>
    from .. import parent
ValueError: attempted relative import beyond top-level package

If we change import in sub/start.py:

- sub/start.py
import relative
import parent

Run it like:

PYTHONPATH='' python3 sub/start.py

Output:

Hello from relative.py
Traceback (most recent call last):
  File "sub/start.py", line 3, in <module>
    import parent
ModuleNotFoundError: No module named 'parent'

Run it like:

PYTHONPATH='.' python3 sub/start.py

Output:

Hello from relative.py
Hello from parent.py

Also it's better to use import from root folder, i.e.:

- sub/start.py
import sub.relative
import parent

Run it like:

PYTHONPATH='.' python3 sub/start.py

Output:

Hello from relative.py
Hello from parent.py

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncarlView Question on Stackoverflow
Solution 1 - PythoneboView Answer on Stackoverflow
Solution 2 - PythonAmirHosseinView Answer on Stackoverflow
Solution 3 - PythonmrgloomView Answer on Stackoverflow