【问题标题】:Relative imports in Python 3Python 3 中的相对导入
【发布时间】:2013-06-03 15:10:05
【问题描述】:

我想从同一目录中的另一个文件中导入一个函数。

有时它对我有用 from .mymodule import myfunction 但有时我会得到:

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

有时它适用于from mymodule import myfunction,但有时我也会得到:

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

我不明白这里的逻辑,也找不到任何解释。这看起来完全随机。

谁能给我解释一下这一切背后的逻辑是什么?

【问题讨论】:

    标签: python python-3.x python-import


    【解决方案1】:

    不幸的是,这个模块需要在包里面,而且它也 有时需要作为脚本运行。知道我怎么能 做到了吗?

    这样的布局很常见...

    main.py
    mypackage/
        __init__.py
        mymodule.py
        myothermodule.py
    

    ...像这样的mymodule.py...

    #!/usr/bin/env python3
    
    # Exported function
    def as_int(a):
        return int(a)
    
    # Test function for module  
    def _test():
        assert as_int('1') == 1
    
    if __name__ == '__main__':
        _test()
    

    ...像这样的myothermodule.py...

    #!/usr/bin/env python3
    
    from .mymodule import as_int
    
    # Exported function
    def add(a, b):
        return as_int(a) + as_int(b)
    
    # Test function for module  
    def _test():
        assert add('1', '1') == 2
    
    if __name__ == '__main__':
        _test()
    

    ...和这样的main.py...

    #!/usr/bin/env python3
    
    from mypackage.myothermodule import add
    
    def main():
        print(add('1', '1'))
    
    if __name__ == '__main__':
        main()
    

    ...当您运行 main.pymypackage/mymodule.py 时工作正常,但由于相对导入,mypackage/myothermodule.py 失败...

    from .mymodule import as_int
    

    你应该运行它的方式是......

    python3 -m mypackage.myothermodule
    

    ...但它有点冗长,并且不能很好地与像 #!/usr/bin/env python3 这样的 shebang 行混合。

    对于这种情况,最简单的解决方法是假设名称 mymodule 是全局唯一的,那就是避免使用相对导入,而只使用...

    from mymodule import as_int
    

    ...虽然,如果它不是唯一的,或者您的包结构更复杂,您需要在PYTHONPATH 中包含包含您的包目录的目录,并这样做...

    from mypackage.mymodule import as_int
    

    ...或者,如果您希望它“开箱即用”,您可以先在代码中使用 PYTHONPATH 这个...

    import sys
    import os
    
    SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
    sys.path.append(os.path.dirname(SCRIPT_DIR))
    
    from mypackage.mymodule import as_int
    

    这有点痛苦,但在 an email 某位 Guido van Rossum 所写的文章中有一个线索...

    我在这个和__main__ 的任何其他提议的小玩意上都是-1 机械。唯一的用例似乎是运行发生的脚本 生活在一个模块的目录中,我一直将其视为一个 反模式。要让我改变主意,你必须说服我 不是。

    在包中运行脚本是否是反模式是主观的,但我个人发现它在我拥有的包含一些自定义 wxPython 小部件的包中非常有用,因此我可以运行任何源文件的脚本以显示wx.Frame 仅包含用于测试目的的小部件。

    【讨论】:

    • 如果您确信您的模块始终具有正确的file,则在a comment of Import a module from a relative path 中给出了获取SCRIPTDIR 的更好方法os.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe()))),您也可以使用os.path.realpath(os.path.dirname(__file__))
    • @marcz:请使用os.path.abspath() 而不是os.path.realpath()。很少需要解析路径上的所有符号链接,这样做实际上会破坏高级符号链接的使用,以便在单个“虚拟”目录中收集正确的包。
    • 请注意,如果函数以下划线“_”开头,则不能导入...
    【解决方案2】:

    说明

    来自PEP 328

    相对导入使用模块的 __name__ 属性来确定 模块在包层次结构中的位置。如果模块的名称 不包含任何包信息(例如,它设置为 '__main__') 然后解析相对导入,就好像模块是顶级模块一样 模块,不管模块在文件中的实际位置 系统。

    在某些时候PEP 338PEP 328 发生冲突:

    ... 相对导入依赖于 __name__ 来确定当前 模块在包层次结构中的位置。在主模块中, __name__ 的值总是 '__main__',所以显式的相对导入 总是会失败(因为它们只适用于包内的模块)

    为了解决这个问题,PEP 366 引入了顶级变量__package__

    通过添加新的模块级属性,此 PEP 允许相对 如果使用 -m 执行模块,导入会自动工作 转变。模块本身中的少量样板将允许 当文件按名称执行时,相对导入起作用。 [...]当它[属性]存在时,相对导入将基于此属性 而不是模块 __name__ 属性。 [...] 当主模块由其文件名指定时,__package__ 属性将设置为 None。 [...] 当导入系统遇到一个显式的相对导入时 未设置 __package__ 的模块(或将其设置为 None),它将 计算并存储正确的值 (__name__.rpartition('.')[0] 对于普通模块__name__对于包初始化模块)

    (强调我的)

    如果__name__'__main__'__name__.rpartition('.')[0] 返回空字符串。这就是错误描述中有空字符串文字的原因:

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

    CPython的PyImport_ImportModuleLevelObject function的相关部分:

    if (PyDict_GetItem(interp->modules, package) == NULL) {
        PyErr_Format(PyExc_SystemError,
                "Parent module %R not loaded, cannot perform relative "
                "import", package);
        goto error;
    }
    

    如果 CPython 在interp->modules(可作为sys.modules 访问)中找不到package(包的名称),则会引发此异常。由于sys.modules“将模块名称映射到已经加载的模块的字典”,现在很明显在执行相对导入之前必须明确地绝对导入父模块强>。

    注意:来自issue 18018的补丁添加了another if block,它将在之前上面的代码执行: p>

    if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
        PyErr_SetString(PyExc_ImportError,
                "attempted relative import with no known parent package");
        goto error;
    } /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
        ...
    */
    

    如果package(同上)为空字符串,错误信息为

    ImportError: attempted relative import with no known parent package
    

    但是,您只会在 Python 3.6 或更高版本中看到这一点。

    解决方案 #1:使用 -m 运行脚本

    考虑一个目录(这是一个 Python package):

    .
    ├── package
    │   ├── __init__.py
    │   ├── module.py
    │   └── standalone.py
    

    package 中的所有文件都以相同的 2 行代码开头:

    from pathlib import Path
    print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
    

    我只包含这两行是为了让操作的顺序更明显。我们可以完全忽略它们,因为它们不会影响执行。

    __init__.pymodule.py 仅包含这两行(即它们实际上是空的)。

    standalone.py 还尝试通过相对导入来导入 module.py

    from . import module  # explicit relative import
    

    我们很清楚/path/to/python/interpreter package/standalone.py 会失败。但是,我们可以使用-m command line option 运行模块,这将“搜索sys.path 以查找命名模块并将其内容作为__main__ 模块执行”

    vaultah@base:~$ python3 -i -m package.standalone
    Importing /home/vaultah/package/__init__.py
    Running /home/vaultah/package/standalone.py
    Importing /home/vaultah/package/module.py
    >>> __file__
    '/home/vaultah/package/standalone.py'
    >>> __package__
    'package'
    >>> # The __package__ has been correctly set and module.py has been imported.
    ... # What's inside sys.modules?
    ... import sys
    >>> sys.modules['__main__']
    <module 'package.standalone' from '/home/vaultah/package/standalone.py'>
    >>> sys.modules['package.module']
    <module 'package.module' from '/home/vaultah/package/module.py'>
    >>> sys.modules['package']
    <module 'package' from '/home/vaultah/package/__init__.py'>
    

    -m 为您完成所有导入工作并自动设置__package__,但您可以在

    解决方案 #2:手动设置 __package__

    请将其视为概念验证而非实际解决方案。它不适合在实际代码中使用。

    PEP 366 有一个解决这个问题的方法,但是,它并不完整,因为单独设置__package__ 是不够的。您将需要在模块层次结构中至少导入 N 个前面的包,其中 N 是父目录的数量(相对于脚本的目录)将搜索正在导入的模块。

    因此,

    1. 将当前模块的第N个前身的父目录添加到sys.path

    2. sys.path删除当前文件的目录

    3. 使用完全限定名导入当前模块的父模块

    4. __package__ 设置为来自 2

    5. 的完全限定名称
    6. 执行相对导入

    我将从解决方案#1中借用文件并添加更多子包:

    package
    ├── __init__.py
    ├── module.py
    └── subpackage
        ├── __init__.py
        └── subsubpackage
            ├── __init__.py
            └── standalone.py
    

    这一次 standalone.py 将使用以下相对导入从 package 包中导入 module.py

    from ... import module  # N = 3
    

    我们需要在该行之前加上样板代码,以使其正常工作。

    import sys
    from pathlib import Path
    
    if __name__ == '__main__' and __package__ is None:
        file = Path(__file__).resolve()
        parent, top = file.parent, file.parents[3]
    
        sys.path.append(str(top))
        try:
            sys.path.remove(str(parent))
        except ValueError: # Already removed
            pass
    
        import package.subpackage.subsubpackage
        __package__ = 'package.subpackage.subsubpackage'
    
    from ... import module # N = 3
    

    它允许我们通过文件名执行standalone.py

    vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
    Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
    Importing /home/vaultah/package/__init__.py
    Importing /home/vaultah/package/subpackage/__init__.py
    Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
    Importing /home/vaultah/package/module.py
    

    可以在here 中找到更通用的封装在函数中的解决方案。示例用法:

    if __name__ == '__main__' and __package__ is None:
        import_parents(level=3) # N = 3
    
    from ... import module
    from ...module.submodule import thing
    

    解决方案 #3:使用绝对导入和setuptools

    步骤是-

    1. 将显式相对导入替换为等效的绝对导入

    2. 安装package 使其可导入

    例如,目录结构可能如下

    .
    ├── project
    │   ├── package
    │   │   ├── __init__.py
    │   │   ├── module.py
    │   │   └── standalone.py
    │   └── setup.py
    

    setup.py 在哪里

    from setuptools import setup, find_packages
    setup(
        name = 'your_package_name',
        packages = find_packages(),
    )
    

    其余文件是从解决方案#1借来的。

    安装将允许您导入包,而不管您的工作目录是什么(假设不会有命名问题)。

    我们可以修改 standalone.py 来利用这个优势(步骤 1):

    from package import module  # absolute import
    

    将您的工作目录更改为project 并运行/path/to/python/interpreter setup.py install --user--user 将软件包安装到your site-packages directory)(步骤2):

    vaultah@base:~$ cd project
    vaultah@base:~/project$ python3 setup.py install --user
    

    让我们验证现在是否可以将 standalone.py 作为脚本运行:

    vaultah@base:~/project$ python3 -i package/standalone.py
    Running /home/vaultah/project/package/standalone.py
    Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
    Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py
    >>> module
    <module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
    >>> import sys
    >>> sys.modules['package']
    <module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>
    >>> sys.modules['package.module']
    <module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
    

    注意:如果您决定走这条路,最好使用virtual environments 单独安装软件包。

    解决方案 #4:使用绝对导入和一些样板代码

    坦率地说,安装不是必需的 - 您可以在脚本中添加一些样板代码以使绝对导入工作。

    我要从 Solution #1 中借用文件并更改 standalone.py

    1. package 的父目录添加到 sys.path之前 尝试使用绝对导入从 package 导入任何内容:

      import sys
      from pathlib import Path # if you haven't already done so
      file = Path(__file__).resolve()
      parent, root = file.parent, file.parents[1]
      sys.path.append(str(root))
      
      # Additionally remove the current file's directory from sys.path
      try:
          sys.path.remove(str(parent))
      except ValueError: # Already removed
          pass
      
    2. 将相对导入替换为绝对导入:

      from package import module  # absolute import
      

    standalone.py 运行没有问题:

    vaultah@base:~$ python3 -i package/standalone.py
    Running /home/vaultah/package/standalone.py
    Importing /home/vaultah/package/__init__.py
    Importing /home/vaultah/package/module.py
    >>> module
    <module 'package.module' from '/home/vaultah/package/module.py'>
    >>> import sys
    >>> sys.modules['package']
    <module 'package' from '/home/vaultah/package/__init__.py'>
    >>> sys.modules['package.module']
    <module 'package.module' from '/home/vaultah/package/module.py'>
    

    我觉得我应该警告你:尽量不要这样做,尤其是如果你的项目结构复杂。


    附带说明,PEP 8 建议使用绝对导入,但指出在某些情况下可以接受显式相对导入:

    建议使用绝对导入,因为它们通常更具可读性 并且往往表现得更好(或至少给出更好的错误 消息)。 [...] 但是,明确的相对导入是可以接受的 绝对导入的替代方案,尤其是在处理复杂的 不必要使用绝对导入的包布局 详细。

    【讨论】:

    • 如果名称为__main__,是否可以手动设置__package__以解决问题?
    • 解决方案 2 的一个小问题是,如果您尝试从命令行作为脚本运行的文件与 __package__ 的值相同,这将失败,因为然后,您尝试运行的文件将优先并被导入而不是包。
    • 您也可以通过文件路径(相对)导入文件:docs.python.org/3/library/…
    • @ArwedMett 你们都可以在我的回答下停止发表您对 Python 导入系统的意见和一般 Python 意见。
    • @boardtc 此声明具有误导性且部分错误。
    【解决方案3】:

    把它放在你的包的 __init__.py 文件中

    # For relative imports to work in Python 3.6
    import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
    

    假设你的包裹是这样的:

    ├── project
    │   ├── package
    │   │   ├── __init__.py
    │   │   ├── module1.py
    │   │   └── module2.py
    │   └── setup.py
    

    现在在你的包中使用常规导入,比如:

    # in module2.py
    from module1 import class1
    

    这适用于 python 2 和 3。

    【讨论】:

    • 我也认为这值得更多投票。把它放在每个__init__.py 中,基本上就可以解决所有相关的导入错误了。
    • 我不能代表其他人,但我倾向于避免修改sys.path,因为我担心它可能会影响其他代码。 (部分原因是我不知道它是如何工作的。)
    • @pianoJames 我知道你的意思,这个(似乎,经过大量的折腾)魔术修复似乎有点太容易了。但它有效。有兴趣不知道这是否有负面影响。
    • 如果你在两个不同的包中有两个同名的模块,这不会导致冲突吗?
    • @ErelSegal-Halevi 我确认一个缺点是如果您有两个来自不同模块的同名文件。运行 python -m pytest 时,我遇到了这个冲突问题。如果作者能提供解决方案就好了。
    【解决方案4】:

    我遇到了这个问题。 hack 解决方法是通过 if/else 块导入,如下所示:

    #!/usr/bin/env python3
    #myothermodule
    
    if __name__ == '__main__':
        from mymodule import as_int
    else:
        from .mymodule import as_int
    
    
    # Exported function
    def add(a, b):
        return as_int(a) + as_int(b)
    
    # Test function for module  
    def _test():
        assert add('1', '1') == 2
    
    if __name__ == '__main__':
        _test()
    

    【讨论】:

    • @Perkins 嗯...在大多数情况下它不会。我认为相对进口可能是个例外。
    • 使用 python 3.8 为我工作。我尝试了许多其他解决方案,这是第一个让我在构建包时在 Emacs 中对文件夹中的文件进行开发的解决方案。使包工作的其他建议对我来说毫无用处,我需要自己开发包。
    • @pauljohn32 如果你正在开发一个包,你应该通过可编辑的 pip install 来测试它(pip install -e . 来自包根目录,setup.py 所在的位置)并将其作为包导入在您的测试文件中,不要像这样干预条件导入。
    • 对我来说最简单的解决方案 (Python 3.9) 不会与 sys.path 混淆。希望它更漂亮(并且不必要),但c'est la vie。 +1
    【解决方案5】:

    系统错误:父模块''未加载,无法执行相对导入

    这意味着您正在将包内的模块作为脚本运行。在包中混合脚本是棘手的,应尽可能避免。使用导入包并运行 scripty 函数的包装脚本。

    如果您的顶级目录名为 foo,它位于您的 PYTHONPATH 模块搜索路径中,并且您在那里有一个包 bar(这是一个您希望在其中包含 __init__.py 文件的目录),脚本不应该放在bar中,而应该放在foo充其量

    请注意,scripts 与此处的 modules 不同之处在于它们被用作python 命令的文件名参数,可以使用python &lt;filename&gt; 或通过@ 987654335@(社邦)专线。它以__main__ module 的形式直接 加载(这就是if __name__ == "__main__": 在脚本中起作用的原因),并且没有包上下文可用于相对导入。

    您的选择

    • 如果可以,请使用setuptools(或poetryflit,这有助于简化打包)打包您的项目,并创建console script entrypoints;使用pip 安装您的项目,然后创建知道如何正确导入包的脚本。您可以使用 pip install -e . 在本地安装您的包,因此仍然可以就地编辑它。

    • 否则,永远,永远,使用python path/to/packagename/file.py,总是使用python path/to/script.pyscript.py可以使用from packagename import ...

    • 作为备用,您可以使用 -m command-line switch 告诉 Python 导入模块并将其用作 __main__ 文件。但是,这不适用于 shebang 行,因为不再有脚本文件。

      如果您使用python -m foo.bar,并且foo/bar.py 位于sys.path 目录中,则该目录将作为__main__ 导入并使用正确的包上下文执行。如果bar 也是一个包,在foo/ 内部,它必须有一个__main__.py 文件(所以foo/bar/__main__.py 作为sys.path 目录的路径)。

    • 在极端情况下,通过直接设置__package__来添加Python用来解析相对导入的元数据;文件foo/bar/spam.py,可作为foo.bar.spam 导入,被赋予全局__package__ = "foo.bar"。它只是另一个全局变量,如 __file____name__,由 Python 在导入时设置。

    开启sys.path

    以上所有要求您的包可以被导入,这意味着它需要在sys.path 中列出的目录(或zipfiles)之一中找到。这里也有几个选项:

    • 找到path/to/script.py 的目录(所以path/to)会自动添加到sys.path。执行python path/to/foo.py 会将path/to 添加到sys.path

    • 如果您将项目打包(使用setuptoolspoetryflit 或其他 Python 打包工具)并安装它,则该包已添加到正确的位置。

    • 作为最后的手段,自己将正确的目录添加到sys.path。如果包可以相对于脚本文件定位,则使用脚本全局命名空间中的__file__ 变量(例如,使用pathlib.Path objectHERE = Path(__file__).resolve().parent 是对文件所在目录的引用,作为绝对路径)。

    【讨论】:

      【解决方案6】:

      对于 PyCharm 用户:

      我也收到了ImportError: attempted relative import with no known parent package,因为我添加了. 符号来消除 PyCharm 解析错误。 PyCharm 错误地报告无法找到:

      lib.thing import function

      如果你把它改成:

      .lib.thing import function

      它使错误静音,但随后您会得到上述ImportError: attempted relative import with no known parent package。只需忽略 PyCharm 的解析器。这是错误的,尽管它说了什么,但代码运行良好。

      【讨论】:

      • 通常IDE中的解析器是错误的,因为它的路径没有设置。您应该找到指定 CWD(当前工作目录)的选项并将其设置为您在命令行上使用的相同内容
      • 在用 Python 和 Pycharm 折腾太多之后,我要去:try: from .mname import symbol except: from mname import symbol
      • @gerardw PyCharm 根据项目目录的基本文件夹查看符号。如果您觉得通常 对您不满意,这意味着您打开项目的位置有问题。您可以尝试在不同的根目录中打开它。这就是西普里安所说的。可能会帮助你 - 但也许不是?
      【解决方案7】:

      为了避免这个问题,我设计了一个使用 repackage 包的解决方案,它已经为我工作了一段时间。它将上层目录添加到 lib 路径:

      import repackage
      repackage.up()
      from mypackage.mymodule import myfunction
      

      使用智能策略(检查调用堆栈),重新打包可以进行适用于各种情况的相对导入。

      【讨论】:

      • 我试过了。仍然失败:ImportError: attempted relative import with no known parent package
      • @pauljohn32 你是如何导入的?此外,up() 在目录层次结构中只进入一级。您需要检查您在那里实际找到的内容。
      • @fraulau。谢谢。我一直在测试。我试图在 up() 之后离开相对导入。这是错误的,我从你的例子中看到了。如果我重写为绝对,那么 up() 似乎与sys.path.append 具有相同的效果,可以在搜索路径中添加“包含文件夹”。然后绝对路径起作用。
      【解决方案8】:

      希望这对那里的人有价值 - 我浏览了六篇 stackoverflow 帖子,试图找出与上面发布的内容类似的相对导入。我按照建议设置了所有内容,但我仍在点击ModuleNotFoundError: No module named 'my_module_name'

      由于我只是在本地开发和玩耍,我还没有创建/运行setup.py 文件。我显然也没有设置我的PYTHONPATH

      我意识到,当我像测试与模块位于同一目录中时一样运行我的代码时,我找不到我的模块:

      $ python3 test/my_module/module_test.py                                                                                                               2.4.0
      Traceback (most recent call last):
        File "test/my_module/module_test.py", line 6, in <module>
          from my_module.module import *
      ModuleNotFoundError: No module named 'my_module'
      

      但是,当我明确指定路径时,事情开始起作用了:

      $ PYTHONPATH=. python3 test/my_module/module_test.py                                                                                                  2.4.0
      ...........
      ----------------------------------------------------------------------
      Ran 11 tests in 0.001s
      
      OK
      

      因此,如果有人尝试了一些建议,认为他们的代码结构正确并且仍然发现自己处于与我相似的情况,如果您不将当前目录导出到 PYTHONPATH,请尝试以下任一方法:

      1. 运行您的代码并明确包含路径,如下所示: $ PYTHONPATH=. python3 test/my_module/module_test.py
      2. 为避免调用PYTHONPATH=.,创建一个setup.py 文件,内容如下,然后运行python setup.py development 将包添加到路径:
      # setup.py
      from setuptools import setup, find_packages
      
      setup(
          name='sample',
          packages=find_packages()
      )
      

      【讨论】:

        【解决方案9】:

        我需要从主项目目录运行 python3 以使其工作。

        例如,如果项目具有以下结构:

        project_demo/
        ├── main.py
        ├── some_package/
        │   ├── __init__.py
        │   └── project_configs.py
        └── test/
            └── test_project_configs.py
        

        解决方案

        我会在文件夹 project_demo/ 中运行 python3,然后执行一个

        from some_package import project_configs
        

        【讨论】:

          【解决方案10】:

          我得到了这个ImportError:尝试使用没有已知父包的相对导入

          在我的程序中,我使用当前路径中的文件来导入其功能。

          from .filename import function
          

          然后我用包名修改了当前路径(Dot)。这解决了我的问题。

          from package_name.filename import function
          

          希望以上回答对你有所帮助。

          【讨论】:

          • 在您的场景中package_name 是什么?
          • @mins 那是我的自定义包。
          【解决方案11】:

          我尝试了以上所有方法都无济于事,才意识到我错误地在我的包名中包含了 -

          简而言之,在__init__.py 所在的目录中不要有-。发现这样的虚无之后,我从来没有感到过得意。

          【讨论】:

          • 这听起来像是一个应该报告的错误。
          • @JohnM。真的,不知何故,我觉得包名中的- 是非法的,或者至少不被接受
          【解决方案12】:

          我的样板文件是在 package 独立运行中创建具有相对导入的 module

          package/module.py

          ## Standalone boilerplate before relative imports
          if __package__ is None:                  
              DIR = Path(__file__).resolve().parent
              sys.path.insert(0, str(DIR.parent))
              __package__ = DIR.name
          
          from . import variable_in__init__py
          from . import other_module_in_package
          ...
          

          现在您可以以任何方式使用您的模块:

          1. 照常运行模块:python -m package.module
          2. 将其用作模块:python -c 'from package import module'
          3. 独立运行:python package/module.py
          4. 或使用 shebang (#!/bin/env python) 只需:package/module.py

          注意!如果您的module 与您的package 同名,则使用sys.path.append 而不是sys.path.insert 将给您带来难以追踪的错误。例如。 my_script/my_script.py

          当然,如果您有来自包层次结构中更高级别的相对导入,这还不够,但在大多数情况下,这还好。

          【讨论】:

          • 感谢@Andor,它帮助我解决了我的问题。对我来说 package 被预设为一个空字符串,所以这个样板条件有效:if not __package__: [set __package__]
          • 工作就像一个魅力。我需要在第 3 行再添加一个 .parent,但这是针对我的嵌套包的情况。无论如何,谢谢!
          • 您需要添加“从 pathlib 导入路径”,否则对我有用。谢谢!
          【解决方案13】:

          从同一目录导入

          首先,您可以从同一目录导入。

          这是文件结构...

          Folder
           |
           ├─ Scripts
           |   ├─ module123.py
           |
           ├─ main.py
           ├─ script123.py
          

          这里是 main.py

          from . import script123
          from Scripts import module123
          

          如您所见,从. 导入从当前目录导入。

          注意:如果使用除 IDLE 以外的任何方式运行,请确保在运行之前将您的终端导航到与 main.py 文件相同的目录。

          此外,从本地文件夹导入也可以。

          从父目录导入

          my GitHub gist here所见,有如下方法。

          取下面的文件树...

          ParentDirectory
           ├─ Folder
           |   |
           |   ├─ Scripts
           |   |   ├─ module123.py
           |   |
           |   ├─ main.py
           |   ├─ script123.py
           |
           ├─ parentModule.py
          

          然后,只需将此代码添加到您的 main.py 文件的顶部即可。

          import inspect
          import os
          import sys
          
          current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
          parent_dir = os.path.dirname(current_dir)
          sys.path.insert(0, parent_dir)
          
          from ParentDirectory import Stuff
          

          【讨论】:

          • 我经常看到像您的 main.py 示例这样的示例,但我永远无法重新创建它们。你愿意看看这个 repo 并告诉我我做错了什么吗? github.com/Adam-Hoelscher/relative-imports-python3
          • 当然!现在就这样做。
          • 您介意在您的存储库上提出问题,并将错误回调粘贴到那里吗?这会比这里的 cmets 更容易。我需要查看错误。
          • 如果我正确理解了您的问题,那就完成了。谢谢。
          • 是的。谢谢
          【解决方案14】:

          TL;DR:对@Aya 的回答,更新为pathlib 库,并适用于未定义__file__ 的Jupyter 笔记本:

          您要导入在../my_Folder_where_the_package_lives/my_package.py 下定义的my_function 关于你在哪里编写代码。

          然后做:

          import os
          import sys
          import pathlib
          
          PACKAGE_PARENT = pathlib.Path(__file__).parent
          #PACKAGE_PARENT = pathlib.Path.cwd().parent # if on jupyter notebook
          SCRIPT_DIR = PACKAGE_PARENT / "my_Folder_where_the_package_lives"
          sys.path.append(str(SCRIPT_DIR))
          
          from my_package import my_function
          

          【讨论】:

          • 我想你的意思是pathlib.Path.cwd().parent而不是Path.cwd().parent
          【解决方案15】:

          如果两个包都在您的导入路径(sys.path)中,并且您想要的模块/类在example/example.py中,那么在没有相对导入的情况下访问该类尝试:

          from example.example import fkt
          

          【讨论】:

            【解决方案16】:

            如果上述方法都不适合您,您可以明确指定模块。

            目录:

            ├── Project
            │     ├── Dir
            │     │    ├── __init__.py
            │     │    ├── module.py
            │     │    └── standalone.py
            

            解决方案:

            #in standalone.py
            from Project.Dir.module import ...
            

            module - 要导入的模块

            【讨论】:

              【解决方案17】:

              我认为最好的解决方案是为您的模块创建一个包: Here 是有关如何操作的更多信息。

              一旦你有了一个包,你就不需要担心相对导入,你可以只做绝对导入。

              【讨论】:

                【解决方案18】:

                我在使用 Django 时经常遇到这种情况,因为很多功能都是从 manage.py 脚本执行的,但我也希望我的一些模块也可以直接作为脚本运行(理想情况下,你会让它们manage.py 指令,但我们还没有)。

                这是这样一个项目的模型;

                ├── dj_app
                │   ├── models.py
                │   ├── ops
                │   │   ├── bar.py
                │   │   └── foo.py
                │   ├── script.py
                │   ├── tests.py
                │   ├── utils.py
                │   └── views.py
                └── manage.py
                

                这里的重要部分是manage.pydj_app/script.pydj_app/tests.py。我们还有子模块dj_app/ops/bar.pydj_app/ops/foo.py,其中包含我们想要在整个项目中使用的更多项目。

                问题的根源通常是希望您的 dj_app/script.py 脚本方法在 dj_app/tests.py 中有测试用例,当您运行 manage.py test 时会调用这些测试用例。

                这就是我设置项目及其imports 的方式;

                # dj_app/ops/foo.py
                # Foo operation methods and classes
                foo_val = "foo123"
                

                .

                # dj_app/ops/bar.py
                # Bar operations methods and classes
                bar_val = "bar123"
                

                .

                # dj_app/script.py
                # script to run app methods from CLI
                
                # if run directly from command line
                if __name__ == '__main__':
                    from ops.bar import bar_val
                    from ops.foo import foo_val
                
                # otherwise
                else:
                    from .ops.bar import bar_val
                    from .ops.foo import foo_val
                
                def script_method1():
                    print("this is script_method1")
                    print("bar_val: {}".format(bar_val))
                    print("foo_val: {}".format(foo_val))
                
                
                if __name__ == '__main__':
                    print("running from the script")
                    script_method1()
                

                .

                # dj_app/tests.py
                # test cases for the app
                # do not run this directly from CLI or the imports will break
                from .script import script_method1
                from .ops.bar import bar_val
                from .ops.foo import foo_val 
                
                def main():
                    print("Running the test case")
                    print("testing script method")
                    script_method1()
                
                if __name__ == '__main__':
                    print("running tests from command line")
                    main()
                

                .

                # manage.py
                # just run the test cases for this example
                import dj_app.tests
                dj_app.tests.main()
                

                .

                manage.py 运行测试用例;

                $ python3 manage.py
                Running the test case
                testing script method
                this is script_method1
                bar_val: bar123
                foo_val: foo123
                

                自行运行脚本;

                $ python3 dj_app/script.py
                running from the script
                this is script_method1
                bar_val: bar123
                foo_val: foo123
                

                请注意,如果您尝试直接运行test.py,则会收到错误消息,因此请不要这样做;

                $ python3 dj_app/tests.py
                Traceback (most recent call last):
                  File "dj_app/tests.py", line 5, in <module>
                    from .script import script_method1
                ModuleNotFoundError: No module named '__main__.script'; '__main__' is not a package
                

                如果我遇到更复杂的导入情况,我通常最终会实施类似的方法来破解它;

                import os
                import sys
                THIS_DIR = os.path.dirname(os.path.realpath(__file__))
                sys.path.insert(0, THIS_DIR)
                from script import script_method1
                sys.path.pop(0)
                

                【讨论】:

                  【解决方案19】:

                  我有一个类似的问题:我需要一个使用通用常量来配合的 Linux 服务和 cgi 插件。这样做的“自然”方法是将它们放在包的 init.py 中,但我无法使用 -m 参数启动 cgi 插件。

                  我的最终解决方案类似于上面的解决方案 #2:

                  import sys
                  import pathlib as p
                  import importlib
                  
                  pp = p.Path(sys.argv[0])
                  pack = pp.resolve().parent
                  
                  pkg = importlib.import_module('__init__', package=str(pack))
                  

                  缺点是必须在常量(或常用函数)前加上 pkg:

                  print(pkg.Glob)
                  

                  【讨论】:

                    【解决方案20】:

                    将要从中导入的文件移动到外部目录会有所帮助。
                    当您的主文件在其自己的目录中创建任何其他文件时,这非常有用。
                    例如:
                    之前:
                    项目
                    |---目录1
                    |-------main.py
                    |-------module1.py
                    之后:
                    项目
                    |---module1.py
                    |---目录1
                    |-------main.py

                    【讨论】:

                      【解决方案21】:

                      TLDR;通过在 python 脚本的入口点添加以下内容,将脚本路径附加到系统路径

                      import os.path
                      import sys
                      PACKAGE_PARENT = '..'
                      SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))
                      sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))
                      

                      现在您可以在 PyCharma 以及终端中运行您的项目了!!

                      【讨论】:

                        【解决方案22】:

                        这是我的项目结构

                        ├── folder
                        |   | 
                        │   ├── moduleA.py
                        |   |   |
                        |   |   └--function1()
                        |   |       └~~ uses function2()
                        |   | 
                        │   └── moduleB.py
                        |       | 
                        |       └--function2()
                        |   
                        └── main.py
                             └~~ uses function1()
                        

                        这里我的 moduleA 导入 moduleBmain 导入 moduleA

                        我在moduleA下面添加了sn-p来导入moduleB

                        try:
                            from .moduleB import function2 
                        except:
                            from moduleB import function2 
                        

                        现在我可以分别执行main.pymoduleA.py

                        这是一个解决方案吗?

                        【讨论】:

                        • 我会使用sys.version_info[0] 来检查您是使用python 2 还是python 3,或者我会将异常绑定到ImportError 。尽管在 python 3 中,相对路径可能不是 python 包,因此该解决方案也可能不起作用。这取决于模块B的位置。 (按照惯例,模块也应该是蛇形的)。
                        【解决方案23】:

                        我遇到了类似的问题,并通过在工作目录中创建一个 符号链接 到该包来解决它:

                        ln -s ../../../my_package my_package

                        然后照常导入:

                        import my_package

                        我知道这更像是“Linux”解决方案,而不是“Python”解决方案。但它仍然是一种有效的方法。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2014-11-10
                          • 1970-01-01
                          • 2016-03-07
                          • 2014-11-21
                          • 1970-01-01
                          相关资源
                          最近更新 更多