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
.