ChatGPT解决这个技术问题 Extra ChatGPT

Python 3 中的相对导入

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

有时它对我有用 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

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

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

有时加1!

E
E-rich

不幸的是,这个模块需要在包中,有时它还需要作为脚本运行。知道我怎么能做到这一点吗?

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

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

这有点痛苦,但有一个线索可以解释为什么在某个 Guido van Rossum 所写的an email中......

我对此和任何其他提议的 __main__ 机器的玩弄都是-1。唯一的用例似乎是运行恰好位于模块目录中的脚本,我一直将其视为反模式。要让我改变主意,你必须说服我它不是。

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


如果您确信您的模块始终具有正确的 file,那么您也可以使用 os.path.realpath(os.path.dirname(__file__)),在 a comment of Import a module from a relative path 中以 os.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe()))) 形式给出了获取 SCRIPTDIR 的更好方法。
@marcz:请使用 os.path.abspath() 而不是 os.path.realpath()。很少需要解析路径上的所有符号链接,这样做实际上会破坏高级符号链接的使用,以便在单个“虚拟”目录中收集正确的包。
请注意,如果函数以下划线“_”开头,则无法导入...
这种反模式的一个有用用例是将测试放在项目的一些子目录中,这些子目录应该测试软件的其他组件?!
v
vaultah

解释

来自PEP 328

相对导入使用模块的 __name__ 属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为'__main__'),那么无论模块实际位于文件系统上的什么位置,相对导入都会像模块是顶级模块一样被解析。

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

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

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

通过添加新的模块级别属性,如果使用 -m 开关执行模块,此 PEP 允许相对导入自动工作。当文件按名称执行时,模块本身的少量样板将允许相对导入工作。 [...]当 [属性] 存在时,相对导入将基于此属性而不是模块 __name__ 属性。 [...] 当主模块由其文件名指定时,__package__ 属性将设置为 None。 [...]当导入系统在没有设置 __package__ (或设置为 None )的模块中遇到显式相对导入时,它将计算并存储正确的值 (__name__.rpartition('.')[0] for普通模块和 __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,它将在上述代码之前执行:

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

包中的所有文件都以相同的 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 是父目录的数量(相对于脚本的目录)将搜索正在导入的模块。

因此,

将当前模块的第 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

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

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

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

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

步骤是 -

用等效的绝对导入替换显式相对导入安装包以使其可导入

例如,目录结构可能如下

.
├── 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:使用绝对导入和一些样板代码

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

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

在尝试使用绝对导入从包中导入任何内容之前,将包的父目录添加到 sys.path: import sys from pathlib import Path # 如果您还没有这样做 file = Path(__file__).resolve() parent, root = file.parent, file.parents[1] sys.path.append(str(root)) # 另外从 sys.path 中删除当前文件的目录 try: sys.path.remove(str(parent)) except ValueError: # Already移除 pass 将相对导入替换为绝对导入: 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__ 以解决问题?
感谢您的快速回复。我正在使用 nltk 包,我收到一个错误:`来自 nltk.decorators 的 中的文件“/usr/local/lib/python3.5/dist-packages/nltk/__init__.py”,第 115 行导入装饰器,记忆文件“/usr/local/lib/python3.5/dist-packages/nltk/decorators.py”,第 23 行,在 sys.path = [p for p in sys.path if "nltk " not in p] 文件 "/usr/local/lib/python3.5/dist-packages/nltk/decorators.py",第 23 行,在 sys.path = [p for p in sys.path if " nltk"不在 p] TypeError: 'PosixPath' 类型的参数不可迭代`
解决方案 2 的一个小问题是,如果您尝试从命令行作为脚本运行的文件与 __package__ 的值同名,这将失败,因为您尝试运行的文件将然后优先并被导入而不是包。
您还可以按文件路径(相对)导入文件:docs.python.org/3/library/…
python -m 为我工作
B
Brian Burns

把它放在你包的 __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 时,我遇到了这个冲突问题。如果作者能提供解决方案就好了。
L
Leon

我遇到了这个问题。 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()

这不是一个很好的解决方案。此外,裸 except: 很糟糕。请改用 except ImportError:
这里是 SystemError。 (Py 3.4)
这不是一个糟糕的主意,但最好检测使用哪个导入而不是尝试/除外。 if __name__ == '__main__': from mymod import as_int; else: from .mymod import as_int 之类的东西。
使用 python 3.8 为我工作。我尝试了许多其他解决方案,这是第一个让我在构建包时在 Emacs 中对文件夹中的文件进行开发的解决方案。使包工作的其他建议对我来说毫无用处,我需要自己开发包。
@pauljohn32 如果您正在开发一个包,您应该通过可编辑的 pip install 安装它(setup.py 所在的包根目录中的 pip install -e .)来测试它,并将它作为一个包导入到您的测试文件中,而不是乱七八糟像这样有条件的进口。
M
Martijn Pieters

SystemError:父模块''未加载,无法执行相对导入

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

如果您的顶级目录称为 foo,它位于您的 PYTHONPATH 模块搜索路径中,并且您在其中有一个包 bar(它是您希望在其中包含 __init__.py 文件的目录),脚本不应放在 bar 内,而应在 foo 最多内继续存在。

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

您的选择

如果可以的话,使用 setuptools(或诗歌或 flit,可以帮助简化打包)打包您的项目,并创建控制台脚本入口点;使用 pip 安装项目然后创建知道如何正确导入包的脚本。你可以使用 pip install -e . 在本地安装你的包,所以它仍然可以就地编辑。

否则,永远不要使用 python path/to/packagename/file.py,总是使用 python path/to/script.py 和 script.py 可以使用 from packagename import ...。

作为后备,您可以使用 -m 命令行开关来告诉 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 中列出的目录(或 zipfile)之一中找到。这里也有几个选项:

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

如果您打包了项目(使用 setuptools、poetry、flit 或其他 Python 打包工具)并安装了它,则该包已经添加到正确的位置。

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


G
Grant Curell

对于 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 根据项目目录的基本文件夹查看符号。如果它对您不利,这通常意味着您打开项目的位置存在一些问题。您可以尝试在不同的根目录中打开它。这就是西普里安所说的。可能会帮助你 - 但也许不是😂
f
fralau

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

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

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


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

希望这对那里的人有价值 - 我浏览了六篇 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,请尝试以下任一方法:

运行您的代码并显式包含路径,如下所示:$ PYTHONPATH=. python3 test/my_module/module_test.py 为了避免调用 PYTHONPATH=.,创建一个 setup.py 文件,内容如下,然后运行 python setup.py development 将包添加到路径:

# setup.py from setuptools import setup, find_packages setup(name='sample', packages=find_packages() )


S
SeF

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
Á
Árthur

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

A
Andor

我在可独立运行的 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
...

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

像往常一样运行模块:python -m package.module 将其用作模块:python -c 'from package import module' 独立运行:python package/module.py 或使用 shebang (#!/bin/env python) 只需:包/模块.py

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

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


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

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

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

from .filename import function

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

from package_name.filename import function

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


您的场景中的 package_name 是什么?
@mins 那是我的自定义包。
C
Cat Mai

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

简而言之,__init__.py 所在的目录中没有 -。在发现这种愚蠢之后,我从未感到高兴。


这听起来像是一个应该报告的错误。
@约翰M。真的,不知何故,我觉得包裹名称中的 - 是非法的,或者至少不赞成
S
SamTheProgrammer

从同一目录导入

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

这是文件结构...

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
当然!现在才这样做。
您介意在您的存储库上提出问题,并将错误回调粘贴到那里吗?这会比这里的评论更容易。我需要查看错误。
如果我正确地理解了你的问题,那么它就完成了。谢谢。
是的。谢谢
S
Salt999

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

from example.example import fkt

A
AlexisG

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

目录:

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

解决方案:

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

module - 要导入的模块


T
Taras Kucherenko

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

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


u
user5359531

我在使用 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)

D
Darvin Rio

这是我的项目结构

├── 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

这是一个解决方案吗?


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

以下解决方案在 Python3 上进行了测试

├── classes
|   |
|   ├──__init__.py
|   | 
│   ├── userclass.py
|   |   |
|   |   └--viewDetails()
|   |       
|   | 
│   └── groupclass.py
|       | 
|       └--viewGroupDetails()
|   
└── start.py
     └~~ uses function1()

现在,为了使用 userclass 的 viewDetails 或 groupclass 的 viewGroupDetails,首先在 classess 目录的 _init _.py 中定义。

例如:在_init _.py

from .userclasss import viewDetails

from .groupclass import viewGroupDetails

Step2:现在,在start.py中我们可以直接导入viewDetails

例如:在 start.py

from classes import viewDetails
from classes import viewGroupDetails

t
tommiport5

我有一个类似的问题:我需要一个使用通用常量来配合的 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)

P
PythonSnek

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


H
Hitesh Sahu

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 以及终端中运行您的项目!


O
OLL

在尝试编写可以作为模块或可执行脚本加载的 python 文件时,我遇到了类似的问题。

设置

/path/to/project/
├── __init__.py
└── main.py
    └── mylib/
        ├── list_util.py
        └── args_util.py

和:

main.py:

#!/usr/bin/env python3
import sys
import mylib.args_util

if __name__ == '__main__':
    print(f'{mylib.args_util.parseargs(sys.argv[1:])=}')

mylib/list_util.py:

def to_int_list(args):
    return [int(x) for x in args]

mylib/args_util.py:

#!/usr/bin/env python3
import sys
from . import list_util as lu

def parseargs(args):
    return sum(lu.to_int_list(args))

if __name__ == '__main__':
    print(f'{parseargs(sys.argv[1:])=}')

输出

$ ./main.py 1 2 3
mylib.args_util.parseargs(sys.argv[1:])=6

$ mylib/args_util.py 1 2 3
Traceback (most recent call last):
  File "/path/to/project/mylib/args_util.py", line 10, in <module>
    from . import list_util as lu
ImportError: attempted relative import with no known parent package

解决方案

我选择了 Bash/Python 多语言解决方案。该程序的 Bash 版本只是调用 python3 -m mylib.args_util 然后退出。

Python 版本会忽略 Bash 代码,因为它包含在文档字符串中。

Bash 版本忽略 Python 代码,因为它使用 exec 停止解析/运行行。

mylib/args_util.py:

#!/bin/bash
# -*- Mode: python -*-
''''true
exec /usr/bin/env python3 -m mylib.args_util "$@"
'''

import sys
from . import list_util as lu

def parseargs(args):
    return sum(lu.to_int_list(args))

if __name__ == '__main__':
    print(f'{parseargs(sys.argv[1:])=}')

输出

$ ./main.py 1 2 3
mylib.args_util.parseargs(sys.argv[1:])=6

$ mylib/args_util.py 1 2 3
parseargs(sys.argv[1:])=6

解释

第 1 行:#!/bin/bash;这是“shebang”线;它告诉交互式 shell 如何运行这个脚本。 Python:忽略(评论) Bash:忽略(评论)

Python:忽略(评论)

Bash:忽略(评论)

第 2 行:# -*- 模式:python -*- 可选;这称为“模式线”;它告诉 Emacs 在读取文件时使用 Python 语法高亮而不是猜测语言是 Bash。 Python:忽略(评论) Bash:忽略(评论)

Python:忽略(评论)

Bash:忽略(评论)

第 3 行:''''true Python:将其视为以 'true 开头的未分配文档字符串\n Bash:将其视为扩展为 true 的三个字符串(其中前两个是空字符串)(即 '' + '' +“真”=“真”);然后它运行为真(什么都不做)并继续到下一行

Python:将其视为以 'true\n 开头的未分配文档字符串

Bash:将其视为扩展为 true 的三个字符串(其中前两个是空字符串)(即 '' + '' + 'true' = 'true');然后它运行为真(什么都不做)并继续到下一行

第 4 行: exec /usr/bin/env python3 -m mylib.args_util "$@" Python:仍然将其视为第 3 行的文档字符串的一部分。 Bash:运行 python3 -m mylib.args_util 然后退出(它没有解析超出此行的任何内容)

Python:仍然将其视为第 3 行的文档字符串的一部分。

Bash:运行 python3 -m mylib.args_util 然后退出(它不会解析超出此行的任何内容)

第 5 行:''' Python:将此视为第 3 行的文档字符串的结尾。 Bash:不解析此行

Python:将其视为第 3 行的文档字符串的结尾。

Bash:不解析这一行

注意事项

这在 Windows 上不起作用: 解决方法:使用 WSL 或批处理包装器脚本调用 python -m mylib.args_util。

解决方法:使用 WSL 或批处理包装器脚本调用 python -m mylib.args_util。

这仅在当前工作目录设置为 /path/to/project/ 时才有效。解决方法:调用 /usr/bin/env #!/bin/bash # -*- Mode: python -*- ''''true exec /usr/bin/env python3 \ PYTHONPATH="$(cd "$ (dirname "$0")/.." ; pwd)" \ -m mylib.args_util "$@" '''

解决方法:调用 /usr/bin/env #!/bin/bash # -*- Mode: python -*- ''''true exec /usr/bin/env python3 \ PYTHONPATH="$(cd "$ (dirname "$0")/.." ; pwd)" \ -m mylib.args_util "$@" '''


R
Ronny

我为 Python 创建了一个新的实验性导入库:ultraimport

它使程序员可以更好地控制导入并使它们明确。当导入失败时,它还会提供更好的错误消息。

它允许您执行始终有效的相对、基于文件系统的导入,无论您如何运行代码,也无论您当前的工作目录是什么。运行脚本或模块都没有关系。您也不必更改可能有其他副作用的 sys.path。

然后你会改变

from .mymodule import myfunction

import ultraimport
myfunction = ultraimport('__dir__/mymodule.py', 'myfunction')

这样,即使您将代码作为脚本运行,导入也将始终有效。

像这样导入脚本时的一个问题是后续的相对导入可能会失败。 ultraimport 有一个内置的预处理器来自动重写相关的导入。


D
David Mendes
# so that it can be ran as a standalone script with Kernel set in project directory
if __name__ == "__main__":
    import os
    import sys
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from other_folder.other_file import useful_function

def hello_world():
    useful_function()
    print("hello world")


if __name__ == "__main__":
    hello_world()

这是我的解决方案。我既可以将其作为独立脚本运行,也可以将其作为包的一部分导入而不会出现问题。

很简单,只有当您尝试将项目目录作为独立脚本运行时,它才会将项目目录添加到您的路径中


M
Mahmoud Khaled

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

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

然后像往常一样导入它:

import my_package

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