4

My folder structure is as follows

./fff
├── __init__.py
├── fg
│   ├── __init__.py
│   └── settings
│       ├── __init__.py
│       └── settings.py
└── obng
    └── test.py

I want to import the settings.py inside fg/settings as a module into the test.py

I have added the line

from ..fg.settings import settings

But when I run it, it gives me the following error

Traceback (most recent call last): File "/mnt/d/Repos/fff/obng/test.py", line 1, in from ..fg.settings import settings ImportError: attempted relative import with no known parent package

This style of relative importing is supported as per https://docs.python.org/3/reference/import.html#package-relative-imports

What am I doing wrong here?

S.B
  • 13,077
  • 10
  • 22
  • 49
Sharath Prakash
  • 186
  • 3
  • 12
  • Relative imports can't go above the __main__ package location (or the folder where you started your python process). If you run `test.py`, you can't import stuff from `fg` in a relative fashion. – GPhilo Jul 08 '21 at 11:21
  • If you run `python test.py` you can't use `from ..`. But if you have another .py file under `fff` and `import test` then that will work. – Natthaphon Hongcharoen Jul 08 '21 at 11:22
  • 1
    The only way to bypass this is add path variable, like `sys.path.insert('/mnt/d/Repos/fff/fg')` and then `import fg.setting` – Natthaphon Hongcharoen Jul 08 '21 at 11:24

4 Answers4

5

Normally you can't use relative imports when you run your python module as main module like python filename.py but there is a hack using __package__ to achieve this. Remember __package__ is how python resolves relative imports:

1- Create a file called __init__.py in your root directory - fff. ( I can see that you have it, I mentioned for completeness)

2- Put this code on top of your test.py module:

if __name__ == '__main__' and not __package__:
    import sys
    sys.path.insert(0, <path to parent directory of root directory - fff>)
    __package__ = 'fff.obng'

Note: sys.path is where python searches for modules to import them.

3- Now place your relative import statement after the code above (inside the if statement, because we don't wanna mess when your test.py is being imported) :

from ..fg.settings import settings

Now you can call you test.py, it will run without problem. I don't recommend using these hacks but showing the flexibility of the language and doing exactly what you wanna do in some cases is beneficial.

Other good solutions: Absolute import I think is easier and cleaner than this. In addition take a look at @Mr_and_Mrs_D's answer another good solution would be to run your module with -m command-line flag.

S.B
  • 13,077
  • 10
  • 22
  • 49
  • 2
    Stop advising to use `sys.path` hacks please – Mr_and_Mrs_D Jul 09 '21 at 11:19
  • 1
    @Mr_and_Mrs_D You're right sir, I've added couple of sentences to make people avoid using hacks. I intended to show possible solution and capability of dynamically tweaking stuffs in python. This was the only solution if he wants to run his scripts by file name like `python filename.py` – S.B Jul 09 '21 at 12:55
  • 3
    I'm a big fan of this answer, please continue advising `sys.path` hacks please! – ayanami Aug 06 '22 at 12:53
5

It is a matter of how you run your project - you should run from the parent directory of the top-level package as in

$ cd ../fff
$ python -m fff.obng.test # note no py

Then relative imports will be resolved correctly. It is an antipattern running a script directly from its folder

Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
  • This solved all my problems. It's as if there are 2 worlds when executing code in Python: Module world and file world. When setting your project up with modules, stick to the module way to run things., which also seems easier/more consistent. Would you have a reference for a description of why running a script directly from its folder is an antipattern ? – Paul P M Nov 24 '22 at 05:45
  • 1
    Thanks @PaulPM - see for instance https://stackoverflow.com/a/23540051/281545 – Mr_and_Mrs_D Nov 24 '22 at 13:27
1

Relative imports are based on the name of the current module. When running

python fff/obng/test.py

the name of test.py will be __main__ and the import will not work.

What will work is having another script called "test.py" outside the fff module that imports the fff.obng.test

fff_top
├── fff
│   ├── fg
│   │   ├── __init__.py
│   │   └── settings
│   │       ├── __init__.py
│   │       └── settings.py
│   ├── __init__.py
│   └── obng
│       ├── __init__.py
│       └── test.py
└── test.py

with fff_top/test.py:

import fff.obng.test

Then, running the "external" test.py should be ok:

python fft_top/test.py

Alternatively, I would recommend dropping relative imports entirely. One way to do this is using a virtual environment for every package you write, using for example the venv library:

python -m venv venv

Then, add a setup.py in the root folder with the content:

from setuptools import setup, find_packages
setup(name="fff", packages=find_packages())

and change the imports in obng/test.py:

from fff.fg.settings import settings

Finally, activate your virtual environment:

source venv/bin/activate

and install your package in editable mode:

pip install -e .

Then, after you have completed all the steps above:

python fff/obng/test.py

should work.

zap
  • 568
  • 2
  • 13
  • Thanks everyone for the answers. Due to time limitations, I just added the absolute path to the settings.py (./...../fg/settings) to the PYTHONPATH variable and then the import worked as expected. I will try out these implementations as well. – Sharath Prakash Jul 09 '21 at 03:27
0

In Linux, you could create a symbolic link:

$ ln -s ../folder1 mymodules
$ python
>>> import mymodules.myfancymodule as fancy