2

I have a folder structure like this, each time I attempt to use relative import, error raises

├── graphics
│   ├── __init__.py
│   ├── A
│   │   ├── __init__.py
│   │   ├── grok.py
│   │   └── spam.py
    └── B
        ├── __init__.py
        └── bar.py


spam.py/
    def func():
        pass
bar.py/
    def f():
        pass

All these codes are tested in grok.py:

from . import spam
# ImportError: cannot import name 'spam'

from .spam import func
# ModuleNotFoundError: No module named '__main__.spam'; '__main__'     
is not a package

from ..B import bar
# ValueError: attempted relative import beyond top-level package

None of the codes below cause any error:

from graphics.A import spam
from graphics.A.spam import func
from graphics.B import bar
from graphics.B.bar import f
Gino Mempin
  • 25,369
  • 29
  • 96
  • 135

1 Answers1

5

I assume that when you say "tested in grok.py" that you are running it like these:

python3 graphics/A/grok.py
python3 A/grok.py
python3 grok.py

From the Python documentation on Packages and Intra-Package References, there is a note there that says:

Note that relative imports are based on the name of the current module. Since the name of the main module is always "__main__", modules intended for use as the main module of a Python application must always use absolute imports.

When you run grok.py, it gets treated as the main module, and imports will only work if you used absolute imports (assuming you made no changes to sys.path, we'll get to that later). You can test that by putting print(__name__) at the start of grok.py, which will print out "__main__".

Your relative imports are actually going to work if you had a separate python file (ex. main.py) under the graphics package that calls your grok module:

├── graphics
│   ├── __init__.py
|   ├── main.py     <<---- add this
│   ├── A
│   ├── B

In main.py, let's just import the grok module:

from A import grok

In grok.py, let's test the relative imports:

from . import spam
spam.spam_func()

from .spam import spam_func
spam_func()

from B import bar
bar.bar_func()

In spam.py:

def spam_func():
    print("spammy")

In bar.py:

def bar_func():
    print("barry")

When you run main.py:

graphics$ python3 main.py
spammy
spammy
barry

You won't get any of the previous errors. The relative imports work. Notice that to import from B, I used from B instead of from ..B. This is because the import paths are from the point of view of main.py. You can test this by adding this at the top of main.py:

import sys
print(sys.path)
# prints a list, ['/path/to/graphics/',...]

If you did from ..B that means /path/to/graphics/../ which of course does not have the B module (hence, you'll get the "attempted relative import beyond top-level package" error)


Now let's say you don't want to use a separate main.py and you want to run grok.py directly. What you can do is manually add the path to the graphics package to sys.path. Then you can do from A and from B in grok.py.

import sys
sys.path.append("/full/path/to/graphics/")

from A import spam
spam.spam_func()

from B import bar
bar.bar_func() 

If you want to go about "hacking" the sys.path, I suggest reading more on sys.path and checking other related posts that discuss ways of adding paths to sys.path.

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135