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

通常,以下工作之一:

from .mymodule import myfunction
from mymodule import myfunction

…但另一个给了我一个错误:

ImportError: attempted relative import with no known parent package
ModuleNotFoundError: No module named 'mymodule'
SystemError: Parent module '' not loaded, cannot perform relative import

这是为什么?


当前回答

解释

来自PEP 328

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

在某一点上,PEP 338与PEP 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函数的相关部分:

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->模块(可作为sys.modules访问)中找到包(包的名称),则会引发此异常。由于sys.module是“将模块名称映射到已加载的模块的字典”,因此现在很明显,在执行相对导入之前,必须显式绝对导入父模块。

注意:第18018期的补丁添加了另一个if块,将在上面的代码之前执行:

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
│   ├── __init__.py
│   ├── module.py
│   └── standalone.py

包中的所有文件都以相同的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命令行选项运行该模块,该选项将“在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'>

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

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

请将其视为概念的证明,而不是实际的解决方案。它不太适合在实际代码中使用。

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

因此

将当前模块的第N个前身的父目录添加到sys.path从sys.path中删除当前文件的目录使用当前模块的完全限定名称导入当前模块的父模块将__package__设置为2中的完全限定名称执行相对导入

我将从解决方案#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

在这里可以找到封装在函数中的更一般的解决方案。示例用法:

if __name__ == '__main__' and __package__ is None:
    import_parents(level=3) # N = 3

from ... import module
from ...module.submodule import thing

解决方案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(),
)

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

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

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

from package import module  # absolute import

将工作目录更改为project,然后运行/path/to/python/interpreter setup.py install--user(--user在站点包目录中安装包)(步骤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'>

注意:如果您决定走这条路,最好使用虚拟环境来单独安装软件包。

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

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

我将从解决方案#1借用文件并更改标准。py:

在尝试使用绝对导入从包中导入任何内容之前,请将包的父目录添加到sys.path:导入系统从pathlib导入路径#(如果尚未这样做)file=路径(__file__).resolve()parent,root=file.parent,file.parents[1]sys.path.append(str(root))#另外,从sys.path中删除当前文件的目录尝试:sys.path.remove(str(父级))除了ValueError:#已删除通过用绝对导入替换相对导入:来自包导入模块#绝对导入

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

…一个肌肉热模块。。。

#!/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()

…还有一个像这样的男人。。。

#!/usr/bin/env python3

from mypackage.myothermodule import add

def main():
    print(add('1', '1'))

if __name__ == '__main__':
    main()

…当您运行main.py或mypackage/mymodule.py时,它工作得很好,但由于相对导入,mypackage/myothermodule.py失败。。。

from .mymodule import as_int

你应该用的方式是。。。

python3 -m mypackage.myothermodule

……但它有些冗长,与#/usr/bin/env python3。

对于这种情况,最简单的解决方法是避免使用相对导入,而只使用。。。

from mymodule import as_int

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

from mypackage.mymodule import as_int

…或者如果你想让它“开箱即用”,你可以先用这个。。。

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

这是一种痛苦,但有一个线索,为什么在一封由某个Guido van Rossum写的电子邮件中。。。

我对这件事和任何其他提议的无聊事都持反对态度__机械唯一的用例似乎是运行发生的脚本生活在模块的目录中,我一直认为反模式。要让我改变主意,你必须说服我它不是。

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

希望这对外面的人来说是有价值的——我浏览了六篇stackoverflow的帖子,试图找出与上面帖子类似的相对进口。我按照建议设置了所有内容,但仍在运行ModuleNotFoundError:没有名为“my_module_name”的模块

由于我只是在本地开发并四处游玩,所以我没有创建/运行setup.py文件。我显然也没有设置我的巨蟒。

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

$ 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,请尝试以下任一操作:

运行代码并显式包含如下路径:$PYTHONPATH=。python3测试/my_module/模块测试.py为了避免调用PYTHONPATH=。,创建一个包含如下内容的setup.py文件,并运行python setup.py开发,将包添加到路径中:

#设置.py从setuptools导入安装程序,find_packages设置(name=“示例”,packages=find_packages())

从同一目录导入

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

这是文件结构。。。

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

这是main.py

from . import script123
from Scripts import module123

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

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

此外,从本地文件夹导入也有效。

从父目录导入

正如我在GitHub中看到的,有以下方法。

采用以下文件树。。。

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

我尝试了以上所有方法,但都没有效果,结果发现我的包名中错误地包含了-。

简而言之,在__init__.py所在的目录中不要有-。在发现这样的无意义之后,我从未感到欢欣鼓舞。

我正在获取此ImportError:尝试在没有已知父包的情况下进行相对导入

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

from .filename import function

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

from package_name.filename import function

我希望上面的答案对你有所帮助。