6

I have a bare bones project structure with mostly empty python files for the sake of testing a concept from an online tutorial:

project
   |--package1
   |     |--__init__.py
   |     |--module1.py
   |
   |--package2
   |     |--__init__.py
   |     |--module2.py
   |
   |--__init__.py

module1.py:

from .package2.module2 import function2

module2.py:

def function2():
    return 0

Running module1.py directly results in this error:

Traceback (most recent call last):
  File "c:\"blahblahblah"\project\package1\module1.py", line 1, in <module>
    from .package2.module2 import function2
ImportError: attempted relative import with no known parent package

I've tried reducing the complexity of the issue by placing module2.py into the project folder itself and modifying the import as my tutorial suggests it would work (from .module2 import function2) but this yields the same error.

side note: I am under the impression the init files are unnecessary for my version of python, but I've added them to keep all my bases covered.

Python version 3.9.1

Any hints would be much appreciated.

Micahstuh
  • 443
  • 1
  • 5
  • 13

3 Answers3

7

You can join the path of the outer package inside your source's path for relative module imports:

import os, sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))

I guess after this, you can directly do:

from package2.module2 import function2
Jarvis
  • 8,494
  • 3
  • 27
  • 58
2

When you execute a Python script, the specific directory where the script is located is added to the path, but not its parent's directory.

You will have to append the root directory to the path, so Python can find package2.

For that, you can use the pathlib module (Python 3.4 or higher), modifying module1.py thus:

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from package2.module2 import function2

The Path.resolve() method returns the absolute path to the file, and with parent.parent you get module1.py's parent directory (the root dir). Therefore, Python will now be able to find package2.

Also, since you are getting the absolute path, the import will work regardless of executing the script from the project dir or from package1.

Diego Miguel
  • 531
  • 4
  • 13
  • That's a little more verbose than I hoped this would be. I was reading this article/tutorial on relative imports: https://realpython.com/absolute-vs-relative-python-imports/ This article made it seem that this could be done without specifying paths. Do you think this author's strategy is possible? – Micahstuh Jan 04 '21 at 13:34
  • You would still get the `ImportError: attempted relative import with no known parent package` because when executing `module1.py`, the `__package__` attribute would be `None`. – Diego Miguel Jan 04 '21 at 18:31
  • 1
    This is explained in [PEP 336](https://www.python.org/dev/peps/pep-0366/). Note the part that says: "To allow relative imports when the module is executed directly, boilerplate similar to the following would be needed before the first relative import statement [...] *Note that this boilerplate is sufficient only if the top level package is already accessible via sys.path*. Additional code that manipulates sys.path would be needed in order for direct execution to work [...]." – Diego Miguel Jan 04 '21 at 18:31
  • 1
    Thanks @Diego! This gives me a more fundamental understanding of what's going on here. – Micahstuh Jan 04 '21 at 19:59
2

The relative imports failed to work because module1.py could not look into it's parent folder for more packages when executed directly.

The correct call required the -m parameter to signify that module1 was a module within a package. My terminal also needed to go up one directory:

PS C:\...\"parent of project folder"> python -m project.package1.module1

Also, module1.py needed to be modified:

from ..package2.module2 import function2
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Micahstuh
  • 443
  • 1
  • 5
  • 13