Python 在将文件用作模块和直接调用时都有效的导入

Python imports that work both when using a file as a module and when called directly

我今天在 Python 中阅读了有关 absolute and relative imports 的内容,我想知道当包含导入的脚本用作模块时是否有一种 pythonic 方法可以使导入工作并且直接调用的时候。例如:

└── project
      ├── main.py
      └── package
             ├── submoduleA.py
             └── submoduleB.py

main.py

from package.submoduleA import submoduleAfunction
submoduleAfunction() 

submoduleB.py

def submoduleBfunction():
    print('Hi from submoduleBfunction')

submoduleA.py

# Absolute import, works when called directly: python3 package/submoduleA.py 
from submoduleB import submoduleBfunction 

# Relative import, works when imported as module: python3 main.py
# from .submoduleB import submoduleBfunction 

def submoduleAfunction():
    submoduleBfunction()
    
if __name__=="__main__":
    submoduleAfunction()

如你所见,我有绝对导入和相对导入。他们每个人都在特定情况下工作。但是有没有一种 pythonic 的方式让它适用于这两种情况?到目前为止,我已经完成了 sys.path 的技巧,但我不确定这是否适用于所有情况(即从解释器或不同的 OS 中调用)或者即使这样反对任何建议。

import sys
import pathlib
sys.path.append(str(pathlib.Path(__file__).parent.resolve()))

一种可能性是将绝对导入包含在 try 块中并捕获 ImportException。如果出现异常,您可能将其用作模块,然后进行相对导入。

try:
    ​from submoduleB import submoduleBfunction 
except ImportException:
    from .submoduleB import submoduleBfunction 

免责声明:

  • 您的情况可能有所不同,我意识到我的回答并未完全回答问题(使用 relative 导入)。 Mea maxima culpa(我的错)。

  • 我的辩护词:谷歌搜索 python 避免相对导入 returns 540k 次点击。

我通常避免相对导入,因为它们似乎在这类事情上有太多特殊情况,而且我非常喜欢 运行宁 python 文件在适当的时候直接作为脚本。

例如,我的 constants.py 文件可以 运行 作为脚本 - 它会 breakpoint() 我可以检查它的内容,其中大部分来自环境变量。

假设 projectparent 目录在您的 Python 路径中,我将只使用 from project.package.submoduleA import submoduleAfunction。这适用于所有情况。

(我发现显式导入的一个 可能 额外好处是当我想重命名我的顶级包时(myprojectmyproject2)。明确一切让我能够快速 sed 更改。请注意 Wim 在下面的评论中的评论)

# Absolute import, works when called directly: python3 package/submoduleA.py 
from submoduleB import submoduleBfunction 

因为 submoduleAsubmoduleB 的“兄弟”,这实际上是一个隐含的相对导入。 style guide 对他们说:

Implicit relative imports should never be used and have been removed in Python 3.

它甚至在 运行 宁 python3 package/submoduleA.py 作为脚本时工作的唯一原因是 Python 将脚本目录添加到 sys.path 之前。来自 docs:

As initialized upon program startup, the first item of this list, path[0], is the directory containing the script that was used to invoke the Python interpreter.

也就是说,目录 /path/to/package 被注入到 sys.path,允许直接导入 submoduleB

绝不应该使用隐式相对导入,那么您应该使用什么呢?没关系,你可以使用绝对导入的正确形式:

from package.submoduleB import submoduleBfunction

或相对导入:

from .submoduleB import submoduleBfunction

只要已将 package 安装到环境中,两者都可以(安装软件包会将代码放入 site-packages,位于 sys.path 上)。

如果您绝对必须 运行 package/submoduleA.py 直接作为脚本,在本例中为 considered an anti-pattern, then you will need to use the absolute import version. Relative imports do not work。您需要改用 python3 -m package.submoduleA 才能使相对导入版本正常工作。