How to import modules from a file in a deeper directory?

孤街醉人 提交于 2020-05-23 21:30:08

问题


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.


回答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)




回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/61322726/how-to-import-modules-from-a-file-in-a-deeper-directory

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!