Python中的相对导入3

时间:2013-06-07 10:26:50

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

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

有时候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

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

有人可以向我解释这一切背后的逻辑是什么吗?

21 个答案:

答案 0 :(得分:404)

  

不幸的是,这个模块需要在包内,它也是   有时需要作为脚本运行。知道我怎么做   实现那个?

这样的布局很常见......

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

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)))

from mypackage.mymodule import as_int

这是一种痛苦,但是有一个线索,为什么在某个Guido van Rossum写的an email中......

  

我在这个以及__main__提出的任何其他提议中都是-1   机械。唯一的用例似乎是运行脚本   住在模块的目录中,我一直认为这是一个目录   反模式。为了让我改变主意,你必须让我相信   它不是。

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

答案 1 :(得分:182)

解释

来自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无法在package中找到interp->modules(包的名称)(可以sys.modules访问),则会引发此异常。由于sys.modules"一个字典,它将模块名称映射到已经加载的模块" ,现在很清楚父模块必须在执行相对导入之前显式绝对导入

注意: issue 18018中的补丁添加了another if block,在代码之前执行以上:

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 __。py module.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. 使用其完全限定名称

  5. 导入当前模块的父模块
  6. __package__设置为 2

  7. 中的完全限定名称
  8. 执行相对导入

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

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

    这次 standalone.py 将使用以下相对导入从包导入 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以使其可导入

    3. 例如,目录结构可能如下

      .
      ├── 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(),
      )
      

      其余文件来自 Solution#1

      安装将允许您导入包,无论您的工作目录如何(假设没有命名问题)。

      我们可以修改 standalone.py 以使用此优势(步骤1):

      from package import module  # absolute import
      

      将您的工作目录更改为project并运行/path/to/python/interpreter setup.py install --user--useryour 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:使用绝对导入和一些样板代码

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

      我要从解决方案#1 借用文件并更改 standalone.py

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

        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
        
      3. 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建议使用绝对导入,但声明在某些情况下可以接受明确的相对导入:

          

        建议使用绝对导入,因为它们通常更具可读性   并且倾向于表现得更好(或者至少提供更好的错误   消息)。 [...]但是,明确的相对进口是可以接受的   替代绝对进口,特别是在处理复杂时   使用绝对导入的包布局是不必要的   冗长。

答案 2 :(得分:39)

将此内容放入您的软件包的__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。

答案 3 :(得分:33)

我遇到了这个问题。黑客解决方法是通过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()

答案 4 :(得分:9)

对于PyCharm用户:

我也得到了from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC ,因为我添加了ImportError: attempted relative import with no known parent package表示法来使PyCharm解析错误静音。 PyCharm错误地报告无法找到:

.

如果将其更改为:

lib.thing import function

它使错误消失,但是随后您得到前面提到的.lib.thing import function。只需忽略PyCharm的解析器即可。这是错误的,尽管说了什么,代码仍然可以正常运行。

答案 5 :(得分:4)

我的样板,用于在 module 可独立运行的情况下制作具有相对导入的 package

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

注意!如果您的 sys.path.append 与您的 {{1} 同名,使用 sys.path.insert 而不是 module 会给您带来难以追踪的错误}.例如。 package

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

答案 6 :(得分:4)

希望这对那里的人有价值 - 我经历了六个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将包添加到路径中:
  3. # setup.py
    from setuptools import setup, find_packages
    
    setup(
        name='sample',
        packages=find_packages()
    )
    

答案 7 :(得分:3)

我收到此 ImportError:尝试进行相对导入,但没有已知的父包

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

from .filename import function

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

from package_name.filename import function

希望以上答案对您有所帮助。

答案 8 :(得分:2)

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

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

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

答案 9 :(得分:2)

如果以上方法都不适合您,则可以显式指定模块。

目录:

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

解决方案:

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

module-要导入的模块

答案 10 :(得分:1)

我需要从主项目目录运行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

答案 11 :(得分:1)

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

from example.example import fkt

答案 12 :(得分:0)

我认为最好的解决方案是为您的模块创建一个软件包: Here提供了更多有关操作方法的信息。

一旦有了软件包,您就不必担心相对导入,那么您就可以进行绝对导入。

答案 13 :(得分:0)

我有一个类似的问题:我需要一个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)

答案 14 :(得分:0)

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

答案 15 :(得分:0)

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中以及在Terminal中运行项目!

答案 16 :(得分:0)

我尝试以上所有方法均无济于事,只是意识到我错误地在软件包名称中使用了-

简而言之,-所在的目录中没有__init__.py。发现这种烦恼之后,我再也不会感到高兴。

答案 17 :(得分:0)

我在使用 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 时被调用。

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

# 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)

答案 18 :(得分:0)

这是我的项目结构

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

这里我的 moduleA 导入 moduleBmain 导入 moduleA

我在 moduleA 中添加了以下代码段以导入 moduleB

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

现在我可以单独执行 main.pymoduleA.py

这是一个解决方案吗?

答案 19 :(得分:0)

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

您想导入在 my_function 下定义的 ../my_Folder_where_the_package_lives/my_package.py 尊重您编写代码的位置。

然后做:

import os
import sys
import pathlib

PACKAGE_PARENT = Path.cwd().parent
SCRIPT_DIR = PACKAGE_PARENT / "my_Folder_where_the_package_lives"
sys.path.append(str(SCRIPT_DIR))

from my_package import my_function

答案 20 :(得分:-3)

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

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

然后照常导入:

import my_package

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