Python 3 中的相对导入

2024-11-19 08:38:00
admin
原创
9
摘要:问题描述:我想从同一目录中的另一个文件导入一个函数。通常,以下方法之一有效:from .mymodule import myfunction from mymodule import myfunction ...但另一个给了我以下错误之一:ImportError: attempted relative imp...

问题描述:

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

通常,以下方法之一有效:

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

这是为什么呢?


解决方案 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.py...当您运行或时工作正常mypackage/mymodule.py,但mypackage/myothermodule.py由于相对导入而失败......

from .mymodule import as_int

您应该运行它的方式是使用 -m 选项并提供 Python 模块系统中的路径(而不是文件系统)...

python3 -m mypackage.myothermodule

...但是它有点冗长,并且与像这样的 shebang 行混合效果不佳#!/usr/bin/env python3

另一种方法是避免使用相对导入,而只使用......

from mypackage.mymodule import as_int

无论哪种方式,您都需要从的父级运行mypackage,或将该目录添加到PYTHONPATH(任何一个都可以确保mypackage位于 sys.path模块搜索路径中)。或者,如果您希望它“开箱即用”,您可以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 的人写的一封电子邮件中可以找到原因的线索...

我对此以及任何其他提议的__main__
机器调整都持 -1 态度。唯一的用例似乎是运行恰好位于模块目录中的脚本,我一直认为这是一种反模式。要让我改变主意,你必须说服我事实并非如此。

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

解决方案 2:

解释

来自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

PyImport_ImportModuleLevelObjectCPython函数的相关部分:

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

如果 CPython 无法在(accessible as )package中找到 (包的名称) ,则会引发此异常。由于是“将模块名称映射到已加载模块的字典”,因此现在很明显,在执行相对导入之前必须明确绝对导入父模块interp->modules`sys.modules`sys.modules

注意:来自问题 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

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

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运行模块,该选项将“搜索命名的模块并将其内容作为模块执行”-msys.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. 设置为2__package__的完全限定名称

  5. 执行相对导入

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

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

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(在您的 site-packages 目录--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中借用文件并更改standalone.py

  1. 在尝试使用绝对导入从包中导入任何内容之前,将的父目录添加到: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
  1. 用绝对导入替换相对导入:

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建议使用绝对导入,但指出在某些情况下显式相对导入是可以接受的:

建议使用绝对导入,因为它们通常更易读并且行为更好(或至少提供更好的错误消息)。[...] 但是,显式相对导入是绝对导入的可接受替代方案,特别是在处理复杂的包布局时,使用绝对导入会不必要地冗长。

解决方案 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 和 Python 3 中都有效。

解决方案 4:

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

解决方案 5:

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

这意味着您正在以脚本的形式运行包内的模块。在包内混合使用脚本比较棘手,应尽可能避免。请使用包装器脚本来导入包并运行您的scripty函数。

如果您的顶级目录名为foo,它位于您的PYTHONPATH模块搜索路径上,并且您在那里有一个包bar(它是您期望__init__.py文件所在的目录),则脚本不应该放在 里面,而应该barfoo 最多存在于 中。

请注意,此处的脚本与模块不同,因为它们被用作python命令的文件名参数,无论是使用python <filename>还是通过(shebang) 行。它直接作为模块#!加载(这就是在脚本中工作的原因),并且没有用于构建相对导入的包上下文。__main__`if name == "__main__":`

您的选择

  • 如果可以,请使用setuptools(或poetryflit,这有助于简化打包)打包您的项目,并创建控制台脚本入口点;使用 安装您的项目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)。

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

sys.path

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

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

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

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

解决方案 6:

总结

您只能相对导入同一包中的另一个模块内的模块。

概念澄清

我们在书籍/文档/文章中看到很多示例代码,它们向我们展示了如何相对导入模块,但是当我们这样做时,它会失败。

原因很简单,我们没有按照 Python 模块机制的期望运行代码,尽管代码写得完全正确。这就像某种运行时的东西。

模块加载取决于你如何运行代码。这就是混乱的根源。

什么是模块?

当且仅当模块被另一个文件导入时,它才是 Python 文件。给定一个文件mod.py,它是一个模块吗?是也不是,如果你运行python mod.py,它就不是一个模块,因为它没有被导入。

什么是包?

包是包含 Python 模块的文件夹。

顺便说一句,__init__.py如果您不需要任何包初始化或自动加载子模块,则从 python 3.3 开始不需要。您不需要__init__.py在目录中放置空白。

这证明只要有文件被导入,包就只是一个文件夹。


真正的答案

现在,这个描述变得更加清晰了。

您只能相对导入同一包中的另一个模块内的模块。

给定一个目录:

. CWD
|-- happy_maker.py # content: print('Sends Happy')
`-- me.py # content: from . import happy_maker

python me.py,我们得到了attempted relative import with no known parent package

me.py直接运行,它不是一个模块,我们不能在其中使用相对导入。

解决方案 1

使用import happy_maker而不是from . import happy_maker

解决方案 2

将我们的工作目录切换到父文件夹。

. CWD
|-- happy
|   |-- happy_maker.py
    `-- me.py

跑步python -m happy.me

当我们处于包含的目录中时happyhappy是一个包,me.py, happy_maker.py是模块,我们现在可以使用相对导入,并且我们仍然想要运行me.py,所以我们使用,-m这意味着将模块作为脚本运行。


Python 习语

. CWD
|-- happy
|   |-- happy_maker.py # content: print('Sends Happy')
|   `-- me.py # content: from . import happy_maker
`-- main.py # content: import happy.me

这个结构是 Python 惯用语。main是我们的脚本,是 Python 中的最佳实践。终于,我们做到了。


兄弟姐妹或祖父母

另一个常见的需求:

.
|-- happy
|   |-- happy_maker.py
|   `-- me.py
`-- sad
    `-- sad_maker.py

我们想要导入sad_makerme.py该怎么做?

首先,我们需要在同一个包中制作happysad,所以我们必须进入上一级目录。然后from ..sad import sad_makerme.py.

就这样。

解决方案 7:

对于 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 的解析器即可。它是错误的,尽管它说了什么,但代码运行良好。

解决方案 8:

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

解决方案 9:

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

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

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

解决方案 10:

希望这对某些人有用 - 我浏览了六篇 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()
)

解决方案 11:

我的样板在可运行的独立系统中进行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代替sys.path.insert会导致难以追踪的错误。例如module`package`my_script/my_script.py

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

解决方案 12:

从同一目录导入

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

这是文件结构...

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

解决方案 13:

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

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

from .filename import function

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

from package_name.filename import function

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

解决方案 14:

对于那些不同意Guido 观点的人,这里有三句话:

import sys
from pathlib import Path
sys.path.append(str(Path(sys.argv[0]).absolute().parent.parent))

希望有帮助。

更新:发现当通过相对路径调用可执行文件时,此操作会失败。解决方案是使用resolve()而不是absolute()

import sys
from pathlib import Path
sys.path.append(str(Path(sys.argv[0]).resolve().parent.parent))

解决方案 15:

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

解决方案 16:

我尝试了以上所有方法但都无济于事,最后我才意识到-我的包裹名称中有一个错误。

-简而言之,目录中没有__init__.py。我从来没有在发现这种无聊的东西后感到高兴过。

解决方案 17:

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

目录:

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

解决方案:

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

module - 要导入的模块

解决方案 18:

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

from example.example import fkt

解决方案 19:

值得注意的是,有时缓存是导致所有问题的原因。

我在将类重新排列到新目录中后尝试了不同的方法,并且在删除 后相对导入开始起作用__pycache__

解决方案 20:

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

一旦您有了一个包,您就不需要担心相对导入,您只需进行绝对导入即可。

解决方案 21:

当我使用 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.py和,dj_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)

解决方案 22:

这是我的项目结构

├── 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.py同时执行两个操作,也可以moduleA.py单独执行

这是一个解决方案吗?

解决方案 23:

我遇到了同样的错误,我的项目结构如下

->project
  ->vendors
    ->vendors.py
  ->main.py

我试图这样打电话

from .vendors.Amazon import Amazom_Purchase

这里抛出了一个错误,所以我只需从语句中删除第一个 . 即可修复它

from vendors.Amazon import Amazom_Purchase

希望这有帮助。

解决方案 24:

以下解决方案在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

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

例如:在 start.py 中

from classes import viewDetails
from classes import viewGroupDetails

解决方案 25:

当我尝试编写一个可以作为模块或可执行脚本加载的 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:忽略(评论)

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

    • Python:忽略(评论)

    • Bash:忽略(评论)

  • 第 3 行:''''true

    • Python:将其视为未分配的文档字符串,开头为`'true
      `

    • Bash:将其视为三个字符串(其中前两个是空字符串),扩展为true(即'' + '' + 'true' = 'true');然后运行true(不执行任何操作)并继续下一行

  • 第 4 行:exec /usr/bin/env python3 -m mylib.args_util "$@"

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

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

  • 第 5 行:'''

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

    • Bash:不解析此行

注意事项

  • 这在 Windows 上不起作用:

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

  • 这仅当当前工作目录设置为时才有效/path/to/project/

    • 解决方法:PYTHONPATH调用时设置/usr/bin/env

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

解决方案 26:

我遇到了类似的问题:我需要一个使用公共常量进行协作的 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)

解决方案 27:

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

解决方案 28:

将要导入的文件移到外部目录会有所帮助。

当您的主文件在其自己的目录中创建任何其他文件时,这非常有用。

例如:

之前:

Project  
|---dir1  
|-------main.py  
|-------module1.py  

后:

Project  
|---module1.py  
|---dir1  
|-------main.py  

解决方案 29:

由于对 setup.py 的误解,我遇到了这个问题。您可能永远不需要这个答案,但是在使用 setup.py 从文件夹安装我的包时,我有这样的配置:

packages=setuptools.find_packages(include=["package_folder.*"])

这是错误的,因为它不应该有

.*

应该是:

packages=setuptools.find_packages(include=["package_folder"])

只是包没有正确安装,与相关导入有偏差。

解决方案 30:

Python 中的相对导入,从一个目录向上

假设您有以下目录结构:

dev/
    my_project/
        config.py
        sandbox/
            test.py

并且您想要cd进入my_project并运行test.py,其中test.py导入config.py。Python 默认不允许这样做:您无法从当前目录上方导入任何内容除非您sys.path.insert()在尝试导入之前先将该上层目录插入到系统路径中。

因此,这是您要拨打的电话:

cd dev/my_project
./sandbox/test.py

下面说明了如何从一个目录test.py导入:config.py

测试.py:

#!/usr/bin/env python3

import os
import sys

# See: https://stackoverflow.com/a/74800814/4561887
FULL_PATH_TO_SCRIPT = os.path.abspath(__file__)
SCRIPT_DIRECTORY = str(os.path.dirname(FULL_PATH_TO_SCRIPT))
SCRIPT_PARENT_DIRECTORY = str(os.path.dirname(SCRIPT_DIRECTORY))
# allow imports from one directory up by adding the parent directory to the 
# system path
sys.path.insert(0, f"{SCRIPT_PARENT_DIRECTORY}")

# Now you can import `config.py` from one dir up, directly!
import config

# The rest of your imports go here...


# The rest of your code goes here...

参考

  1. 我最初发布此内容的原始答案:如何获取当前正在执行的 python 文件的路径和名称?

进一步

  1. 在 Bash 中,通过使用 可以显得更 Pythonicif [ "$__name__" = "__main__" ],相对导入更加自然,操作如下:

    1. [主要资源] 我网站上的文章:GabrielStaples.com:如何在 Bash 中编写、导入、使用和测试库?

    2. 我的答案:从 shell 脚本导入函数

    3. 我的回答:如何从脚本本身获取 Bash 脚本所在的目录?

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用