2

let's say my project's structure looks like:

project
├── important.py
└── files
    └── file1.py

and the file important.py contains the class Important. How can I import the class(Important) from file1, while file1 is the python file which being executed?.

The only solution I found was to add this code, but I wonder if there is a cleaner way:

import sys; sys.path.append("..")
from important import Important

Things I have tried without success:

from project.important import Important
# ModuleNotFoundError: No module named 'project'
# But it does work inside PyCharm (Why is that?)
from ..important import Important
# ValueError: attempted relative import beyond top-level package

And this errors were keep showing even if I added a __init__.py file inside the project's directory.

import to say is that I am looking for a solution that will fit any machine, as I want to share this project on github to the public.

Ohav
  • 371
  • 2
  • 13
  • don't you need __init.py__ file in project dir? – marke Apr 20 '20 at 12:49
  • @marke why would he need an `__init__.py` file in this scenario? – Grajdeanu Alex Apr 20 '20 at 13:02
  • Possible duplicate of [beyond top level package error in relative import](https://stackoverflow.com/questions/30669474/beyond-top-level-package-error-in-relative-import) et al. – mkrieger1 Apr 20 '20 at 13:16
  • from docs: `The __init__.py files are required to make Python treat directories containing the file as packages.` src: https://docs.python.org/3/tutorial/modules.html#packages – marke Apr 20 '20 at 13:30
  • In the top of your question, your file directory looked like the module was named: project/, not project. Is this a typo? – User 12692182 Apr 20 '20 at 13:43
  • @User12692182 yes. fixing it – Ohav Apr 20 '20 at 20:01
  • @marke so it's not the case in my project, right? – Ohav Apr 21 '20 at 07:48
  • @Ohav I was mistaken, since Python 3.3 you don't need to add a `__init__.py` file to mark dir as a Python package (more: https://stackoverflow.com/questions/37139786/is-init-py-not-required-for-packages-in-python-3-3). – marke Apr 21 '20 at 08:25

3 Answers3

1

You will need to make a reference to parent folder within sys.path. This can be done explicitly inside the code, like you have done, which is not really unpythonic. It can also be done from outside the code, e.g. by modifying the system variable PYTHONPATH or by installing your module within python.

I strongly discourage to use the absolute path, as suggested by other responses, because then, the code will only work on your machine. It is tolerable for student projects, but it is bad practice in real life development since multiple people will work on it, it will execute on test/production/sandbox servers, etc...

So the approach is correct. However, I still suggest modifying slightly your syntax because there are cases in which it will not work as expected:

all_projects/
└── current_project/
    ├── important.py
    └── files
        └── file1.py

$ cd /path/to/all_projects/current_project/files/
$ python file1.py
####  > Ok, thanks to the line sys.path.append("..")

$ cd /path/to/all_projects/current_project/
$ python files/file1.py
####  > Ok, because python implicitly add the execution path to sys.path

$ cd /path/to/all_projects/
$ python current_project/files/file1.py
#### > ModuleNotFoundError: No module named 'important'

Instead, use the following:

import sys, os
sys.path.append(os.path.dirname(sys.path[0]))

Or if file1.py could even be imported from another file, the following is even safer:

import sys, os
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

Finally, for good development practice, I also suggest to put these lines into a different file, called e.g. _set_path. The reason is that you can reuse this in future files, and if you want to modify the code architecture, you only need to change one file:

        ├── file1.py
        ├── file2.py
        ├── ...
        └── _set_path.py

Then, from file1.py, you can use:

import _set_path
from important import Important

(Answer inspired from Python: Best way to add to sys.path relative to the current running script)

Tawy
  • 579
  • 3
  • 14
1

People have pointed out the sys.path.append("..") route. While this works there is also an alternative method with os.chdir('..')

You can view a list of your path with the following command python3 -m site. When importing packages Python checks for modules in all of those paths.
The first element in your sys.path is the current working directory.

There may be a scenario where you don't want to have your current working directory be part of the path and want to have one folder structure up added to path.

An "issue" is there are multiple ways of importing the same thing. For instance you have:

project/
├── important.py
└── files
    ├── file1.py
    └── file2.py

By doing sys.path.append("..") and running the program via python3 file1.py you can import file2 via import file2 or from files import file2. This doesn't look nice and you might start write inconsistent code while not understanding how import properly works.

You can stick with sys.path.append("..")if it works. You won't do much wrong with it. It is a common approach many people do. There may just be a special scenario where you might run into problems which is why many people prefer the os.chdir() approach.

For example in both folder, top folder and subfolder, you have python modules which share the same name. You want to import Python modules from one folder up but not the python modules in the current folder.


Example of os.chdir() in action:

Tin@ubuntu:~/Desktop/tmp/test$ python3
Python 3.6.8 (default, Oct  7 2019, 12:59:55) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.getcwd()
'/home/Tin/Desktop/tmp/test'
>>> import helloworld
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'helloworld'
>>> os.chdir('..')
>>> import helloworld
hello world!
>>> os.getcwd()
'/home/Tin/Desktop/tmp'

Now you can import from one directory up and the import is no longer ambiguous.

Note that while I wrote os.chdir('..') you can do what @Tawy did.

import os
os.chdir(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
# OR:
os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))

It looks confusing but with this you can internally imagine what your current working directory is and base all your import statements on that. It also gives you a consistent current working directory when you are executing your script from all sorts of subdirectories but expect a certain working directory.
You may also do the mistake of running os.chdir('..') twice which will make you go two folders structure up.


In short:

Least complicated solution is sys.path.append(".."). A cleaner solution would be os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')) with the .. being the relative location to your wanted working directory.

Tin Nguyen
  • 5,250
  • 1
  • 12
  • 32
0

Instead of:

import sys

sys.path.append("..")

You could do:

import sys

sys.path.append("/path/to/project")

Which is the same as the first one, but less confusing since you're adding the absolute path and make it obvious for the user.

Note that you don't need any __init__.py file for the above to work. Also, note that this might not be the only way but it's the one that I find to be the most clean.

Also, if you really hate this solution, you might think of restructuring the project so that you can avoid this scenario.

Tin Nguyen
  • 5,250
  • 1
  • 12
  • 32
Grajdeanu Alex
  • 388
  • 6
  • 20
  • PyCharm does not add cwd to your path. Python does that. You can run a python file directly that prints out `sys.path` and you'd see Python added the path automatically. – Tin Nguyen Apr 20 '20 at 13:02
  • @TinNguyen what you talking bout? Pycharm is adding the path to your project automatically to the path, not the Python. – kederrac Apr 20 '20 at 13:03
  • Create a file named `check.py` put into the file `import sys; print(sys.path)` execute the script with `python3 check.py`. See how your current working directory was added automatically to path. PyCharm was not used. – Tin Nguyen Apr 20 '20 at 13:07
  • @TinNguyen please try not to confuse OP even more. Try to reproduce OPs case and run `python3 file1`. See if `/path/to/project/` is added to `sys.path` :) – Grajdeanu Alex Apr 20 '20 at 13:11
  • @GrajdeanuAlex. Thanks for that. In my case I want the project to be shared on GitHub so other people can clone and use it, so I guess your suggestion won't work. – Ohav Apr 20 '20 at 13:22
  • @Ohav well, in that case, you might create a `config.py` file at `project/` level and just declare there: `PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))`. Then add that to your `sys.path` list and you're good to go. Or, if you don't want to do that, just add `os.path.dirname(os.path.dirname(os.path.realpath(__file__)))` to your path in `file1.py` – Grajdeanu Alex Apr 20 '20 at 13:25
  • I didn't comment because your reply wasn't what I was arguing about (notice the `cwd` part) but now you are confusing OP. What OP did with `sys.path.append("..")` works perfectly fine. I verified this myself. That's a proper solution to OP's problem. – Tin Nguyen Apr 20 '20 at 13:36
  • @TinNguyen that's exactly why I wrote in my answer: "_Which is the same as the first one_". – Grajdeanu Alex Apr 20 '20 at 13:40
  • The comment I replied to is confusing. It tells OP to do a bunch of complicated thing instead of sticking to his original solution. – Tin Nguyen Apr 20 '20 at 13:43