【问题标题】:How can I create imports that always work?如何创建始终有效的导入?
【发布时间】:2015-09-13 19:17:14
【问题描述】:

在我的一个项目中建立一个工作结构时,我有点挣扎。问题是,我在这样的结构中有主包和子包(我省略了所有不必要的文件):

code.py
mypackage/__init__.py
mypackage/work.py
mypackage/utils.py

utils.py 有一些通常只在 mypackage 包中使用的实用程序代码。

我通常每个模块文件都有一些测试代码,它调用当前模块的一些方法并打印一些东西来快速检查一切是否正常。此代码位于文件末尾的if __name__ == "__main__" 块中。所以我直接通过import utils 包含了utils.py。例如 mypackage/work.py 看起来像:

import utils

def someMethod():
    pass

if __name__ == "__main__":
    print(someMethod())

但是现在当我在父包(例如code.py)中使用这个模块并像这样导入它时

import mypackage.work

我收到以下错误:

ImportError: No module named 'utils'

经过一番研究,我发现可以通过将mypackage/ 文件夹添加到 PYTHONPATH 环境变量来解决此问题,但这对我来说感觉很奇怪。没有其他方法可以解决这个问题吗?我听说过相对进口,但python docs about modules 中提到了这一点

请注意,相对导入基于当前模块的名称。由于主模块的名称始终为“ma​​in”,因此用作 Python 应用程序主模块的模块必须始终使用绝对导入。

有什么建议可以让我在子模块中有一个if __name__ == "__main__" 部分,并且还可以使用父包中的这个文件而不会弄乱导入?

编辑:如果我在 work.py 中使用相对导入,如导入实用程序的答案中所建议的那样:

from . import utils

我收到以下错误:

SystemError: Parent module '' not loaded, cannot perform relative import

【问题讨论】:

  • 当他尝试运行模块时,这将导致“尝试在非包中进行相对导入”。
  • @flammi88 你能描述一下你是如何运行它的以及从哪个文件夹运行它吗?
  • @sureshvv:我正在从文件所在的目录运行模块。例如,mypackage 目录中的 work.py 和父目录中的 code.py。
  • @flammi88:查看我的更新答案。我能够复制您的情况并在 python2 和 3 下成功运行它。

标签: python python-3.x


【解决方案1】:

不幸的是,子模块的相对导入和直接运行不能混合使用。

mypackage 的父目录添加到您的PYTHONPATH,或者当您想要运行子模块时始终将cd 添加到父目录中。

那么你有两种可能:

使用绝对导入 (from mypackage import utils) 而不是相对导入 (from . import utils) 并像以前一样直接运行它们。该解决方案的缺点是您总是需要编写完全限定的路径,这使得以后重命名 mypackage 等工作变得更加困难。

运行 python3 -m mypackage.utils 等来运行您的子模块,而不是运行 python3 mypackage/utils.py

这可能需要一些时间来适应,但这是更正确的方式(包中的模块与独立脚本不同),您可以继续使用相对导入。

还有更多涉及__package__sys.path 的“神奇”解决方案,但它们都需要在每个文件的顶部添加额外的代码,并带有您想要直接运行的相对导入。我不会推荐这些。

【讨论】:

    【解决方案2】:

    你应该创建一个这样的结构:

    flammi88
    ├── flammi88
    │   ├── __init__.py
    │   ├── code.py
    │   └── mypackage
    │       ├── __init__.py
    │       ├── utils.py
    │       └── work.py
    └── setup.py
    

    然后至少把这个放在 setup.py 中:

    import setuptools
    from distutils.core import setup
    
    setup(
        name='flammi88',
        packages=['flammi88'],
    )
    

    现在,从包含 setup.py 的目录中运行

    pip install -e .
    

    这将使 flammi88 包在开发模式下可用。现在你可以说:

    from flammi88.mypackage import utils
    

    无处不在。这是在 Python 中开发包的常规方法,它解决了所有相关的导入问题。话虽如此,Guido 不赞成子包中的独立脚本。使用这种结构,我会将测试移动到 flammi88/tests/... 中(并使用例如 py.test 运行它们),但有些人喜欢将测试放在代码旁边。

    更新:

    描述 external requirements 并创建您要运行的子包的 executables 的扩展 setup.py 可能如下所示:

    import setuptools
    from distutils.core import setup
    
    setup(
        name='flammi88',
        packages=['flammi88'],
        install_requires=[
            'markdown',
        ],
        entry_points={
            'console_scripts': [
                'work = flammi88.mypackage.work:someMethod',
            ]
        }
    )
    

    现在,在 pip 安装你的包之后,你可以在命令行输入work

    【讨论】:

    • 嗯,我不确定这是否正确,但我之前从未听说过pip的开发模式。我将检查有关此的文档,但没有更简单的方法吗?我目前正在考虑将测试排除到另一个文件并使用相对导入,因为它们听起来是解决问题的正确方法......
    • pip install -e ..python setup.py develop 的作用相同,但如果您想最终将其放入requirements.txt 文件中(只需将-e ... 放入文件中),会更方便。它是一个目录和两个额外的文件:__init__.py 文件可以是空的,setup.py 文件是 6 行——没那么复杂。
    • 是的,但这有一个问题,您需要执行命令才能使项目可运行。目前,您可以克隆 github Repo,当您安装了所有必需的第三方软件包时,一切都会正常工作......不要误会我的意思,这是目前对我的问题的最佳答案。我只是好奇 python 在文件夹边界上的导入有问题,这在 C/C++ 中没有问题。 python系统在很多方面都优于这个,我不明白为什么当您将模块称为主模块时,相对导入不起作用......但是:谢谢!
    • "Nobody" 将像那样处理您的存储库(克隆并处理它)。关键短语是“如果他们安装了所有必需的第三方软件包”,意思是,它不仅仅是克隆和运行.. 它是克隆-运行-安装-缺少-deps-运行-安装-运行-安装-.. . setup.pyrequirements.txt 是 Python 用来传达项目外部依赖关系的方法。有了那些存在,那么它只是克隆安装并运行。所以你真的为你的用户节省了很多精力。 ps:Python 的相对导入没有问题——问题在于直接运行子模块。
    【解决方案3】:

    work.py 中导入utils,如下所示:

    import mypackage.utils
    

    或者如果您想使用较短的名称:

    from mypackage import utils
    

    编辑:如果您需要在包外运行work.py,请尝试:

    try:
        from mypackage import utils
    except ImportError:
        import utils
    

    【讨论】:

    • 这会在尝试直接运行work.py(从mypackage 目录)时导致ImportError: No module named mypackage
    • 抱歉,但这显然是一个非常黑暗的 hack :-D 每次我在包层次结构中添加另一层时,它都会增长到无限长。
    【解决方案4】:

    用途:

    from . import utils
    

    按照彼得的建议

    在你的 code.py 中你应该使用:

    from mypackage import work
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-28
      • 1970-01-01
      相关资源
      最近更新 更多