从不同层次的层次结构导入Python模块

4

在我的Git仓库的顶层目录下,我有以下文件结构:

miscellaneous Dockerfiles, readme, etc
Code/
    training.py
    data/
        generate.py
        tasksets.py

有时候我想在任务集模块中运行任务集脚本时,从tasksets模块中导入generate模块,所以tasksets会包含以下导入操作:
import generate

有时候我想从training模块中导入tasksets模块,因此training模块包含以下导入:

import tasksets

然而,这个设置给我带来了问题。tasksets可以在我将其作为脚本运行时成功导入generate,但如果我在运行training作为脚本时在training中导入tasksets会出现错误(我认为是因为training无法在默认路径中找到generate作为脚本)。我尝试查看各种其他StackOverflow问题和答案,使用__init__.py文件、相对导入等。目前,我的解决方法是在tasksets中使用以下行:
if __name__ == "__main__": import generate
else: from data import generate

但这种方法感觉不太对(我的IDE也不喜欢)。请问有人能解释一下如何使用正确的__init__.py文件和导入语句,使我在将tasksets作为脚本运行时能够导入generate,并且在将training作为脚本运行时也能导入tasksets吗?

1
你能不能在 data/ 目录下放一个空的 __init__.py 文件。这样你就可以在 training.py 中写 from data import generate,在 tasksets.py 中写 from . import generate。这可能是一个解决方案吗? - user4923309
@be-ndee 我刚试了一下。我在 tasksets.py 中用 from . import generate 替换了我的解决方法,将空的 __init__.py 文件添加到 data/ 目录中,并将 tasksets 作为脚本运行,结果出现以下错误:ImportError: cannot import name 'generate' - Jake Levi
1个回答

5

最好使用经典的Python模块/包架构。

projectname/
    __init__.py
    __main__.py
    data/
        __init__.py
        generate.py
        tasksets.py

要使用您的应用程序,请进入projectname/../目录(上一级projectname/)并运行python -m projectname。这将执行projectname/__main__.py
__main__.py中,您将编写类似以下内容的代码:
from projectname.data import generate
from projectname.data import tasksets

if __name__ == '__main__':
    generate.foo()
    tasksets.bar()
  1. 您将使用绝对导入路径(以您的模块名称和一个点“projectname.”开头)
  2. 您将在if __name__ == '__main__'之外导入子模块。
  3. __main__.py 将是您应用程序/脚本的唯一入口点。

在任何其他文件中,您将使用相同的语法和路径导入其他模块:

data/generate.py:

from projectname.data import tasksets

def foo():
    print('SPAM!')
    tasksets.bar()

有些东西我不是很喜欢,但我不确定是否有任何PEP否认它。

在你的projectname/__init__.py文件中,你可以编写:

from projectname.data import generate
from projectname.data import tasksets

因此,您的两个子模块将被导入到主作用域__init__.py中,因此您可以从该作用域导入子模块,例如:

data/generate.py:

from projectname import generate

但是,我并不喜欢这种做法(因为明确比隐式更好)


最后但并非最不重要的:

  • 你也可以使用python projectname/__main__.py命令,但我仍然推荐使用python -m projectname
  • 你可以使用setuptools创建一个setup.py文件,在系统上“安装”你的应用程序,只需运行projectname命令即可运行它。

谢谢@Arount的回答!请问你在第二个建议中,from projectname.data import generatefrom projectname.data import tasksets应该放在哪个__init__.py文件中?是在data文件夹里面那个吗?(在其他的解释中,知道要把什么放在哪个__init__.py文件中曾经让我感到困惑。) - Jake Levi
我同意要避免使用 * 通配符导入,但是将显式导入放在 __init__.py 中的替代版本对我很有吸引力,因为我理想情况下希望保持当前的文件结构,因为它否则非常适合我的项目(据我所知)。 - Jake Levi
@JakeLevi 我更新了projectname/__init__.py这一部分,使它更加明显。另外,如果你真的想保留你当前的文件命名,你可以将__main__.py重命名为training.py,但你的目录名称(在你的示例中为 Code)仍将用作包名称(from Code.data import ...),并且你需要使用python -m Code.training来运行你的代码。我不认为这是最好的方案,因为它非常令人困惑,但应该能够运行。 - Arount

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接