英文:
Calling a module from different directories in python
问题
有以下项目目录结构:
项目
├── src
| ├── eval.py
| └──utils.py
└── app.py
在 eval.py
中,我从 utils
导入一个函数:
from utils import clean_txt
通常情况下,我这样运行代码:
python src/eval.py
我有另一个文件 (app.py
),它将调用 eval.py
来运行项目。运行时,我遇到了这个错误:
> from utils import reading_jobs_for_id_api
> ModuleNotFoundError: No module named 'utils
这可能是因为 utils.py
在 Project
目录中不可见。我找到了解决方法,通过在父目录中运行以下内容。这是解决问题的“正确”方式吗(在编写 Python 包时)?
export PYTHONPATH="${PYTHONPATH}:$PWD"
英文:
I have the following project dir structure:
Project
├── src
| ├── eval.py
| └──utils.py
└── app.py
In eval.py
, I import a function from utils:
from utils import clean_txt
and usually, I run my code like this:
python src/eval.py
I have another file (app.py
) that will call eval.py
to run the project. When I run it, I get this error:
> from utils import reading_jobs_for_id_api
> ModuleNotFoundError: No module named 'utils
It's probably because utils.py
is not seen in the Project
directory. I found a solution to solve this by running the following in the parent directory. Is this the "right" way (when writing python packages) to solve the problem?
export PYTHONPATH="${PYTHONPATH}:$PWD"
答案1
得分: 1
简短回答:不,这不是正确的结构,也不是Python程序的正确“入口”。在导入/使用子模块时——这是您的情况中eval所在的位置,Python首先必须导入包的父级——这应该在包结构的根目录中。
长回答:假设你想要“Project”作为你的包的名称。在这种情况下,你需要从那里“开始”。所以当你准备你的包,比如创建pyproject.toml文件时,你将在可从你的包的根目录看到的某个地方指定你的包的“入口点”。请参考这个:https://packaging.python.org/en/latest/tutorials/packaging-projects/
此外,src可能应该封装Project,而不是反过来,而app.py应该放在Project目录中。除非你的目标是创建一个名为Project的包,其中包含一个名为src的子模块。那样也可以。
至于为什么你的src/eval.py看不到你的app.py模块,那是因为你还没有构建你的包。再次参考上面链接的打包教程。你可以通过在你的模块中打印sys.path的内容轻松验证为什么:
import sys
if __name__=="__main__":
print(sys.path)
在构建/安装你的包之前,sys.path将只包含标准库和你的src/
目录的路径。它不会在任何其他地方查找模块。但如果你安装它,你将看到已将其他路径添加到那个路径列表中,并且你的eval.py将能够执行对app.py的相对导入(除非你还将eval导入到app中,那么你可能会遇到循环导入的问题)。
你还可以通过手动将路径添加到sys.path进行测试:sys.path.append(<到包含app.py的目录的路径>)
。
英文:
Short answer: No, that's not proper structure and that's not proper "entry" for python program. When importing/using submodule - which is what eval is in your case, python first has to import the parent of the package - which should be in root directory of your package structure.
Long answer:
Let's say you want "Project" to be the name of your package. In such case, you need to "start" from there. So when you prepare your package, let's say by creating pyproject.toml file, you will be specifying the "entry point" of your package somewhere that's visible from the root of your package. Please refer to this: https://packaging.python.org/en/latest/tutorials/packaging-projects/
Moreover, src probably should be encapsulating Project, not the other way around and app.py should go into the Project directory. Unless your aim was to create a package called Project with submodule called src. Then that's fine.
As for the reason why your src/eval.py is not seeing your app.py module, that's because you have not built your package. Again, refer to the packaging tutorial linked above for that. You can easily verify why exactly by printing contents of sys.path within your modules:
import sys
if __name__=="__main__":
print(sys.path)
Before building/installing your package, sys.path will only contain paths with standard libraries and your src/
directory. It won't be looking for modules anywhere else. But if you install it then you will see that additional path has been added to that list of paths and your eval.py will be able to perform relative import of app.py (unless you're also importing eval into app, then you might end up with circular imports).
You can also test this by manually adding a path to sys.path: sys.path.append(<path to directory with app.py>)
.
答案2
得分: 1
在app.py中导入from utils import reading_jobs_for_id_api
之前,请确保完整路径到src文件夹在sys.path中。您可以在终端或环境变量中运行脚本之前使用set PYTHONPATH=%PYTHONPATH%;FULL_PATH_TO_SRC_FOLDER
,或者在代码中导入之前输入以下内容:
import sys
sys.path.append(FULL_PATH_TO_SRC_FOLDER)
我个人更喜欢最后一种选项,因为在计算机上永久设置PYTHONPATH
变量会使其变得很长,每次需要从非常不同的位置导入时都要将路径附加到其中。在终端中设置它将使这个变量仅在其中可用。如果将整个项目移动到将来的其他位置,您可以传递sys.path.append
中的os.path.join(Path(__file__).parent, 'src)
- 这将是最灵活的解决方案。
请记住,在将来Python搜索导入的模块时,遵循这个顺序:
- 调用脚本的目录
- 添加到PYTHONPATH变量的路径
- 与Python安装文件夹相关的默认目录
- 在代码中添加到sys.path的路径
英文:
Before importing from utils import reading_jobs_for_id_api
in app.py please make sure that full path to the src folder is in sys.path. You can set PYTHONPATH=%PYTHONPATH%;FULL_PATH_TO_SRC_FOLDER
in terminal or Environment Variables before running the script or above the import in your code you can type this:
import sys
sys.path.append(FULL_PATH_TO_SRC_FOLDER)
I personally prefer the last option cause setting PYTHONPATH
variable permanently on the computer would make it very long if you append path to it every time you need to import something from very different location. Setting it in terminal will make this variable available only in it. If you are going to move whole project somewhere else in the future you can pass in the sys.path.append
os.path.join(Path(__file__).parent, 'src)
- it will be the most flexible solution.
Please remember in the future, that Python searches for imported module in this order:
- directory of the invoked script
- paths added to PYTHONPATH variable
- default directories related to Python installation folder
- paths added to sys.path in the code
答案3
得分: 1
我遇到过相同的问题。使用sys.path.append是其中之一的解决方案,但并不简单。我开发了一个新的模块,syspend。请放置SYSPEND_ROOT文件:空文件。https://pypi.org/project/syspend/
项目
├── src
| ├── eval.py
| └──utils.py
├── app.py
└── SYSPEND_ROOT
在app.py、eval.py中,您可以使用相同的代码导入utils。
import syspend # sys.path.append(directory: SYSPEND_ROOT file exists)
from src.util import clean_txt
英文:
I had a same issue. Using sys.path.append is one of solution, but it isn't simple.
I developed new module, syspend. Please put SYSPEND_ROOT file: empty file.
https://pypi.org/project/syspend/
Project
├── src
| ├── eval.py
| └──utils.py
├── app.py
└── SYSPEND_ROOT
in app.py, eval.py, your can import utils with same code.
import syspend # sys.path.append(directory: SYSPEND_ROOT file exists)
from src.util import clean_txt
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论