relative import in python 3.9.5
Asked Answered
T

4

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?

Teets answered 8/7, 2021 at 11:12 Comment(3)
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.Forthright
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.Quixote
The only way to bypass this is add path variable, like sys.path.insert('/mnt/d/Repos/fff/fg') and then import fg.settingQuixote
N
7

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

Neukam answered 9/7, 2021 at 11:24 Comment(2)
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 ?Synapse
Thanks @PaulPM - see for instance https://mcmap.net/q/14496/-absolute-import-module-in-same-packageNeukam
C
6

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.

Cluff answered 8/7, 2021 at 13:11 Comment(3)
Stop advising to use sys.path hacks pleaseNeukam
@Neukam 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.pyCluff
I'm a big fan of this answer, please continue advising sys.path hacks please!Placeeda
S
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.

Suzysuzzy answered 8/7, 2021 at 12:26 Comment(1)
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.Teets
M
0

In Linux, you could create a symbolic link:

$ ln -s ../folder1 mymodules
$ python
>>> import mymodules.myfancymodule as fancy
Microsurgery answered 4/1, 2023 at 17:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.