5

Whatever I try, since I switched to Python 3 I can run scripts with imports only from the root folder of the project, but not from subfolders. I am aware that there are lots of questions here about the error messages I get, but the proposed solutions don't work for me. Could someone please provide a sample solution for this small sample project? I am sure it would be appreciated by many.

proj
├── foofolder
│   ├── __init__.py
│   └── foofile.py
├── subfolder
│   ├── __init__.py
│   └── run.py
└── __init__.py

I define the function foofun() in foofile.py, and want to call it in run.py.

If run.py is directly in proj it works. But (just to keep things organized) I want to have it in a subfolder - which surprisingly seems impossible.

The annoying thing is that autocomplete in my IDE (PyCharm) suggests that from foofolder.foofile import foofun should work. But it does not. Nor does any other import I could imagine:

from foofolder.foofile import foofun --> ImportError: No module named 'foofolder'

from .foofolder.foofile import foofun --> SystemError: Parent module '' not loaded, cannot perform relative import (Same with two dots at the beginning.)

from proj.foofolder.foofile import foofun --> ImportError: No module named 'proj'

Not even the absolute import works. Why does it not find proj?

sys.path is: ['/parent/proj/subfolder', '/parent/env/lib/python35.zip', '/parent/env/lib/python3.5', '/parent/env/lib/python3.5/plat-x86_64-linux-gnu', '/parent/env/lib/python3.5/lib-dynload', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/parent/env/lib/python3.5/site-packages']

I use Python 3.5.2 in a virtualenv.


Edit: I was wrong when I suggested that this problem is specific to Python 3. Problem and solution were the same when I checked in Python 2.7.12.

The solutions given by Pevogam work. The first one literally adds just '..' to 'sys.path', which describes the parent folder of wherever run.py is executed. The second explicitly adds '/parent/proj'.

Watchduck
  • 1,076
  • 1
  • 9
  • 29

3 Answers3

2

The quickest way I can think about doing this from the top of my head would be:

# subfolder/run.py
import sys
sys.path.append("..")

from foofolder.foofile import foofun

foofun()

Notice however that this will only work if you run run.py from its folder. A more elaborate way that does not depend on this would be

# subfolder/run.py
import sys
import os.path as o
sys.path.append(o.abspath(o.join(o.dirname(sys.modules[__name__].__file__), "..")))

from foofolder.foofile import foofun

foofun()

where you could reach the function from any location. The easiest thing to do for all such modules is to use the same pointer to the root package which in your cases happens by adding "..". You can then perform any imports from the perspective of this root package.

I tend to avoid using relative imports since they are confusing and reduce readability but hopefully some of this helps.

[*] Some IDEs may perform their own project scanning which is the reason they might still find the import as long as you run the python code within the IDE.

pevogam
  • 536
  • 1
  • 6
  • 15
  • 1
    Thanks! A simple solution like this is what I was hoping for. Still, I am surprised that I have to do something that feels a bit hacky to achieve something so normal. I would like to do things _the right way™_ — as long as that intersects with my desire to have related things neatly in the same folder. I find it counter-intuitive btw that after adding `'/parent/proj'` to `sys.path` the absolute import `from proj.foofolder.foofile` still does not work. What would achieve that? – Watchduck Mar 07 '18 at 20:05
  • 1
    I think the general intent is that once you install your project it will be accessible from a *site-packages* folder in the standard PYTHONPATH and thus imports will work from everywhere. In the meantime while you develop you could use the IDE. Adding '/parent/proj' to `sys.path` would imply you can import all modules or packages within 'proj' but not 'proj' itself. Adding '/parent' to `sys.path` would allow you to import 'proj' and its subpackages. – pevogam Mar 07 '18 at 21:03
2

Q: How run python script in subfolder?

A: Call using PYTHONPATH=. python3 subfolder/run.py

Add to bashrc: alias pys = PYTHONPATH=. python3. Then pys subfolder/run.py works!

Comment: For me a script is something that just executes tasks. It is never called from something else or imported. I have many of them. Some of them are similar (e.g. taskA_reset.py, taskB_reset.py). I prefer to organize this as taskA/reset.py et cetera. But, then it is no longer possible to run them! But, prepending the path is a simple solution and it does not require me to change any of my python-code.

Hunaphu
  • 589
  • 10
  • 11
0

The solution I had looked for is the -m switch:

$ python -m proj.subfolder.run

(Note the dots instead of slashes.)

I am always glad, when I can avoid complications like sys.path.append.

See also: What is the purpose of the -m switch?


In a Django project I found this also useful:

$ python manage.py shell < app/utils/scripts/script.py 

Found here: How to execute a Python script from the Django shell?

Watchduck
  • 1,076
  • 1
  • 9
  • 29