如何构建项目内部的导入以适用于脚本和模块?
我有一个相当简单的设置:
[FOLDER]
|-> [Lib]
__init__.py (__all__=["modA","modB"])
modA.py (contains class named classA)
modB.py (contains class named classB + from modA import classA)
test1.py (from Lib.modA import classA
from Lib.modB import classB)
|-> [example]
test2.py (import sys
sys.path.append("../")
from Lib.modA import classA
from Lib.modB import classB)
从 Lib 文件夹运行 test1.py
工作完美,没有错误。另一方面,从示例文件夹运行 test2.py
需要 sys-patch 才能找到 Lib
;然而,它随后崩溃,并通过 from Lib 追溯到
。modB.py
中的 from modA import classA
,No module named modA
。 modB 在 test2.py
中导入 classB
应该如何在模块中定义导入,以便无论可能使用/导入所述模块的任何未来脚本的可能位置如何,它都可以正常工作?
I have a rather simple setup:
[FOLDER]
|-> [Lib]
__init__.py (__all__=["modA","modB"])
modA.py (contains class named classA)
modB.py (contains class named classB + from modA import classA)
test1.py (from Lib.modA import classA
from Lib.modB import classB)
|-> [example]
test2.py (import sys
sys.path.append("../")
from Lib.modA import classA
from Lib.modB import classB)
Running test1.py
from the Lib folder works perfectly without errors. Running test2.py
from the example folder on the other hand requires the sys-patch to find Lib
at all; however, it then crashes with No module named modA
tracing back to the from modA import classA
in modB.py
via from Lib.modB import classB
in test2.py
.
How is one supposed to define an import in a module such that it will also work irrespective of the possible location of any future script that may use/import said module?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
Python 程序应该被视为包和模块,而不是目录和文件。虽然存在一些重叠,但包和模块更具限制性,但也因此封装得更好。
混合两者 – 例如通过手动修改 sys.path – 只能作为最后的手段。
TLDR:
from Lib.modA import classA
/from .modA import classA
而不是from modA import classA
。通过
PYTHONPATH
而不是sys.path
添加搜索路径。首先确定哪些是顶级包。
这是我们从“目录/文件”转到“包”的点。值得注意的是,我们以后不能“超越”顶层,因此它应该包含我们需要的一切。但是,我们也不能删除“下面”的任何内容,因此它应该是一个足够严格的选择。
在示例中,我们可以低至将 modA 和同级视为自己的模块包,高至包含整个项目的
FOLDER
。选择 Lib 是合理的,因为它代表一个独立的部分。移至
FOLDER
的较高位置会过多,移至modA
的较低位置会破坏兄弟姐妹之间的归属关系。顶级包文件夹下的所有内容现在都属于该包。
值得注意的是,
FOLDER/Lib/modA.py
现在是模块Lib.modA
。它不是FOLDER.Lib.modA
,也不仅仅是modA
。仅使用绝对或相对限定名称导入包内容。
现在顶层已经建立,所有
import
语句都必须针对它进行。仅通过其限定名称引用模块,即使两个模块共享一个更通用的父模块:为了避免键入整个完全限定名称,可以使用相对名称代替。这会重用当前模块名称来派生要导入的模块的完全限定名称。
注意:相对导入是包操作,而不是文件系统操作。人们无法超越顶层,但只能深入到包命名空间。
模块内的所有内容现在都已封装且独立。
所有合格的导入都将起作用,而不管包本身的位置如何。只要顶层可以导入,那么它下面的所有内容也都可以导入。
值得注意的是,无论未来可能使用/导入该包的脚本的位置如何,这都有效。
使用绝对限定名称运行打包代码
在包内执行代码时,Python 必须知道该代码是包的一部分。因此,它必须以其绝对限定名称运行。使用 -m 开关 来执行此操作:
通过以下方式启用包 :环境,而不是程序。
定义包的要点是获取代表库的单个独立实体。人们可以压缩一个包或类似的东西,但它仍然是一个包。
作为回报,这意味着代码本身不应该通过直接添加/更改目录来搜索包来破坏这种抽象。
环境(脚本、用户或整个机器)应该公开包,而不是让脚本假定包的位置。基本上有两种方法可以做到这一点:
PYTHONPATH
适用于这。Python programs should be thought of as packages and modules, not as directories and files. While there is some overlap, packages and modules are more restrictive but also better encapsulated as a result.
Mixing both – say by manually modifying
sys.path
– should only be done as a last resort.TLDR:
from Lib.modA import classA
/from .modA import classA
instead offrom modA import classA
.Add search paths via
PYTHONPATH
instead ofsys.path
.Start by deciding which ones are the top-level packages.
This is the point where we go from "directories/files" to "package". Notably, we cannot go "above" the top-level later on, so it should contain everything we need. However, we cannot remove anything "below" either, so it should be a tight enough selection.
In the example we could go as low as treating
modA
and siblings as their own module-package, and as high asFOLDER
encompassing the entire project.It is reasonable to pick
Lib
since it represents a self-contained part. Going higher toFOLDER
would be excessive, going lower tomodA
and siblings would break their relation as belonging together.Everything under the top-level package folder now belongs to the package.
Prominently,
FOLDER/Lib/modA.py
is now the moduleLib.modA
. It is notFOLDER.Lib.modA
, nor justmodA
.Import package content only with absolute or relative qualified names.
Now that the top-level is established, all
import
statements must be made with regard to it. Refer to modules by their qualified name only, even when two modules share a more common parent:In order to avoid typing out the entire fully qualified name, one can use a relative name instead. This reuses the current module name to derive the fully qualified name of the module to import.
Note: Relative imports are a package operation, not a filesystem operation. One cannot go beyond the top-level, but descend into package namespaces.
Everything inside the module is now encapsulated and self-contained.
All qualified imports will work irrespective of the location of the package itself. As long as the top-level can be imported, everything below it can be imported as well.
Notably, this works irrespective of the location of any future script that may use/import the package.
Run packaged code with absolute qualified names
When executing code inside a package, Python has to know that the code is part of the package. Thus, it must be run with its absolute qualified named. Use the -m switch to do so:
Enable packages via the environment, not the program.
The point of defining a package is getting a single, self-contained entity that represents a library. One could zip up a package or similar, and it would still be a package.
In return, this means that the code itself should not break this abstraction by directly adding/changing directories to search packages.
Instead of having scripts assume the location of the package, the environment - of the script, the user, or the entire machine – should expose the package. There are basically two ways to do this:
PYTHONPATH
is appropriate for this.据我了解,当您尝试手动运行时,没有设置路径作为环境,而当您这样做时
python 文件.py
python 不知道我们在哪个环境中运行,这正是我们执行 sys.path.append 将同级目录路径添加到 env 的原因。
如果您使用的是spyder或pycharm等IDE。您可以选择设置程序所在的目录,或者在 pycharm 中,它将整个程序创建为一个包,其中所有路径都由 IDE 处理。
To what I understand, when you are trying to run manually there is no path set as the environment, and when you just do
python file.py
python doesn't know in which environment we are running, which is precisely why, we do sys.path.append, to add the sibling directory path to env.
In the case if you are using an IDE like spyder or pycharm. you have an option to set the directory in which your program is located, or in pycharm it creates the whole program as a package where all the paths are handled by IDE.