1

Forgive me for another "relative imports" post but I've read every stack overflow post about them and I'm still confused.

I have a file structure that looks something like this:


|---MyProject
|  |---constants.py
|  |---MyScripts
|  |    |---script.py

Inside of script.py I have from .. import constants.py

And of course when I run python3 script.py, I get the error ImportError: attempted relative import with no known parent package.

From what I understand, this doesn't work because when you run a python file directly you can't use relative imports. After hours of searching it seems that the most popular solution is to add "../" to sys.path and then do import constants.py but this feels a little hacky. I've got to believe that there is a better solution than this right? Importing constants has got to be a fairly common task.

I've also seen some people recommend adding __init__.py to make the project into a package but I don't really understand how this solves anything?

Are there other solutions out there that I've either missed or just don't understand?

Dallin Davis
  • 421
  • 7
  • 18
  • Does this answer your question? [Relative imports in Python 3](https://stackoverflow.com/questions/16981921/relative-imports-in-python-3) – mkrieger1 Jun 22 '21 at 22:32
  • 2
    Yes, adding `__init__.py` is a prerequisite for `MyProject` being a package at all. It solves that in order to do relative imports there needs to be a package. – mkrieger1 Jun 22 '21 at 22:32
  • Have you read and understood [this answer](https://stackoverflow.com/a/14132912) already? – mkrieger1 Jun 22 '21 at 22:35

4 Answers4

2

Your library code should live in a package. For your folder to be a package, it needs to have __init__.py.

Your scripts can live in the top directory in development. Here is how you would structure this layout:

|---MyProject
|  |---myproject
|  |   |---__init__.py
|  |   |---constants.py
|  |---script.py

If you were to productionize this, you could place scripts into a special folder, but then they would rely on the package being installed on your system. The layout could look like this:

|---MyProject
|  |---myproject
|  |   |---__init__.py
|  |   |---constants.py
|  |---scripts
|  |   |---script.py

In both cases your imports would be normal absolute (non-relative) imports like

from myproject.constants import ...

OR

from myproject import constants

You are correct that attempting a relative import or a path modification in a standalone script is hacky. If you want to use the more flexible second layout, make a setup.py install script in the root folder, and run python setup.py develop.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
1
|---MyProject
|  |---__init__.py # File1
|  |---constants.py

|  |---MyScripts
|  |    |---__init__.py # File 2
|  |    |---script.py

And then the content of File1 __init__.py is:

from . import constants
from . import MyScripts

And then the content of File2 __init__.py is:

from . import script

By converting MyProject to a package, you would be able to import constants like:

from MyProject import constants
# Rest of your code

After that, you need to add the root of your MyProject to your python path. If you are using PyCharm, you do not need to do anything. By default the following line is added to your Python Console or Debugging sessions:

import sys
sys.path.extend(['path/to/MyProject'])

If you are not using PyCharm, one is is to add the above script before codes you run or for debugging, add that as a code to run before every session, or you define a setup.py for your MyProject package, install it in your environment and nothing else need to be changed.

aminrd
  • 4,300
  • 4
  • 23
  • 45
0

The syntax from .. import x is only intended to be used inside packages (thus your error message). If you don't want to make a package, the other way to accomplish what you want is to manipulate the path.

In absolute terms, you can do:

import sys
sys.path.append(r'c:\here\is\MyProject')
import constants

where "MyProject" is the folder you described.

If you want to use relative paths, you need to do a little more work:

import inspect
import os
import sys

parent_dir = os.path.split(
    os.path.dirname(inspect.getfile(inspect.currentframe())))[0]
sys.path.append(parent_dir)
import constants
Ilya
  • 466
  • 2
  • 14
0

To put it concisely, the error message is telling you exactly what's happening; t's just not explaining why. .. isn't a package. It resolves to MyProject but MyProject isn't a package. To make it a package, you must include an “__init__.py” file directly in the “MyProject” directory. It can even be an empty file if you're feeling lazy.

But given what you've shown us, it doesn't seem like your project is supposed to be a package. Try changing the import in script.py to import constants and running python3 MyScripts/script.py from your project directory.

Tyler Crompton
  • 12,284
  • 14
  • 65
  • 94