Python中的相对导入3

Joh*_*nal 589 python python-import python-3.x

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

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

SystemError: Parent module '' not loaded, cannot perform relative import
Run Code Online (Sandbox Code Playgroud)

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

SystemError: Parent module '' not loaded, cannot perform relative import
Run Code Online (Sandbox Code Playgroud)

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

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

Aya*_*Aya 455

不幸的是,这个模块需要在包内,并且它有时也需要作为脚本运行.知道如何实现这一目标吗?

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

main.py
mypackage/
    __init__.py
    mymodule.py
    myothermodule.py
Run Code Online (Sandbox Code Playgroud)

......有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()
Run Code Online (Sandbox Code Playgroud)

...... 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()
Run Code Online (Sandbox Code Playgroud)

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

#!/usr/bin/env python3

from mypackage.myothermodule import add

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

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

...当你运行的正常工作main.pymypackage/mymodule.py,但失败mypackage/myothermodule.py,由于相对进口...

from .mymodule import as_int
Run Code Online (Sandbox Code Playgroud)

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

python3 -m mypackage.myothermodule
Run Code Online (Sandbox Code Playgroud)

...但它有点冗长,并且与像shebang这样的线条混合不好#!/usr/bin/env python3.

对于这种情况最简单的修复,假设名称mymodule是全局唯一的,将避免使用相对导入,只需使用...

from mymodule import as_int
Run Code Online (Sandbox Code Playgroud)

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

from mypackage.mymodule import as_int
Run Code Online (Sandbox Code Playgroud)

...或者如果你想让它"开箱即用",你可以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
Run Code Online (Sandbox Code Playgroud)

这有点痛苦,但有一个线索,为什么在某个Guido van Rossum写的电子邮件中 ......

我在这个以及任何其他提议的__main__ 机械装置上都是-1 .唯一的用例似乎是运行脚本,这些脚本恰好位于模块的目录中,我一直将其视为反模式.为了让我改变主意,你必须让我相信它不是.

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

  • 这是我见过的有关 Python 的最令人悲伤的事情。 (97认同)
  • 这种 _antipattern_ 的一个有用用例是将您的测试放在项目的某些子目录中,这些子目录应该测试软件的其他组件?! (47认同)
  • python3 应该真正改进导入,对此没有好的和干净的解决方案 (17认同)
  • 这是荒唐的。我需要 Python 政治学位才能理解为什么我不能在自学的同一目录中导入类。 (17认同)
  • 让我感到困惑的是,在像 Python 这样的“现代”语言中,导入竟然如此糟糕。几乎所有其他语言都以(某种程度上)理智的方式解决了这个问题。在 Python 中,我发现我不断地处理突然出现的新导入问题,并且没有简单的解决方案。 (10认同)
  • @Student,这就是我试图解决的确切用例以及我是如何来到这里的 (10认同)
  • 获取SCRIPTDIR的更好方法在[从相对路径导入模块的注释]中给出(http://stackoverflow.com/questions/279237/import-a-module-from-a-relative-path?lq= 1#comment15918105_6098238)作为`os.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe())))``如果你确信你的模块总是有一个正确的`文件`你也可以使用`os .path.realpath(os.path.dirname(__ __文件))`. (7认同)
  • Guido Van Rossum 显然从未听说过一种叫做 Jupyter Notebooks 的东西...... (6认同)
  • @marcz:请使用 `os.path.abspath()` 而不是 `os.path.realpath()`。很少需要解析路径上的所有符号链接,这样做实际上可能会破坏高级符号链接的使用,以在单个“虚拟”目录中收集正确的包。 (6认同)
  • “ ...我一直以来都将其视为反模式。”我不知道它是如何反模式的...看起来简单地使相对导入直观地工作似乎非常方便。我只希望能够导入我知道在同一目录中的内容。我不知道他的理由是 (3认同)
  • 您可以通过应用更短且可读的代码段来扩展PYTHONPATH:sys.path.append(os.path.join(os.path.dirname(__ file__),os.path.pardir))` (2认同)

vau*_*tah 220

说明

来自PEP 328

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

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

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

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

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

(强调我的)

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

SystemError: Parent module '' not loaded, cannot perform relative import
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

如果CPython无法packageinterp->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) {
    ...
*/
Run Code Online (Sandbox Code Playgroud)

如果package(与上面相同)是空字符串,则会出现错误消息

ImportError: attempted relative import with no known parent package
Run Code Online (Sandbox Code Playgroud)

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

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

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

.
??? package
?   ??? __init__.py
?   ??? module.py
?   ??? standalone.py
Run Code Online (Sandbox Code Playgroud)

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

from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
Run Code Online (Sandbox Code Playgroud)

包括这两行,以使操作顺序明显.我们可以完全忽略它们,因为它们不会影响执行.

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

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

from . import module  # explicit relative import
Run Code Online (Sandbox Code Playgroud)

我们很清楚/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'>
Run Code Online (Sandbox Code Playgroud)

-m为你做所有导入的东西并自动设置__package__,但你可以自己做

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

请将其视为概念证明而非实际解决方案.它不适合在真实世界的代码中使用.

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

从而,

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

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

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

  4. 设置__package__为完全合格的名字从2

  5. 执行相对导入

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

package
??? __init__.py
??? module.py
??? subpackage
    ??? __init__.py
    ??? subsubpackage
        ??? __init__.py
        ??? standalone.py
Run Code Online (Sandbox Code Playgroud)

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

from ... import module  # N = 3
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

它允许我们通过filename 执行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
Run Code Online (Sandbox Code Playgroud)

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

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

from ... import module
from ...module.submodule import thing
Run Code Online (Sandbox Code Playgroud)

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

步骤是 -

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

  2. 安装package以使其可导入

例如,目录结构可以如下

.
??? project
?   ??? package
?   ?   ??? __init__.py
?   ?   ??? module.py
?   ?   ??? standalone.py
?   ??? setup.py
Run Code Online (Sandbox Code Playgroud)

setup.py是哪里的

from setuptools import setup, find_packages
setup(
    name = 'your_package_name',
    packages = find_packages(),
)
Run Code Online (Sandbox Code Playgroud)

其余的文件都是从解决方案#1中借来的.

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

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

from package import module  # absolute import
Run Code Online (Sandbox Code Playgroud)

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

vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
Run Code Online (Sandbox Code Playgroud)

让我们验证现在可以将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'>
Run Code Online (Sandbox Code Playgroud)

注意:如果您决定沿着这条路走下去,那么最好使用虚拟环境来隔离安装软件包.

解决方案#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
    
    Run Code Online (Sandbox Code Playgroud)
  2. 用绝对导入替换相对导入:

    from package import module  # absolute import
    
    Run Code Online (Sandbox Code Playgroud)

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'>
Run Code Online (Sandbox Code Playgroud)

我觉得我应该警告你:尽量不这样做,特别是如果你的项目结构复杂的话.


作为附注,PEP 8建议使用绝对进口,但声明在某些情况下可以接受明确的相对进口:

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

  • 使用像 Node.js 这样的语言来完成“如此琐碎”的事情,要添加的复杂性实在是令人恼火。客观地说,Python 中的导入系统做得非常糟糕。我阅读了有关此的 [PEP 文档](https://peps.python.org/pep-0328/),他们基本上说“用户提出了更好的解决方案,但这会带来太大的改变”,基本上承认他们导入功能设计得很糟糕,现在“真正”修复它已经太晚了。我们甚至在每个目录中都需要一个 __init__.py ,这一事实是如此愚蠢,当你第一次了解到它时很难相信它是真实的。 (52认同)
  • 我来这里是为了快速而简单的解决方案,但现在看看答案的长度让我对这个导入问题有多么令人困惑感到困惑。 (15认同)
  • 这就是为什么人们说:“Python 是如此简单” (6认同)
  • @SyedShaharyaarHussain 让我惊讶的是这个问题已经存在了 9 年,并且今天仍然很受欢迎,有新的评论和新的答案。 (4认同)
  • 如果名称是`__main__`以便解决问题,是否可以手动设置`__package__`? (3认同)

am5*_*am5 60

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

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

假设你的包是这样的:

??? project
?   ??? package
?   ?   ??? __init__.py
?   ?   ??? module1.py
?   ?   ??? module2.py
?   ??? setup.py
Run Code Online (Sandbox Code Playgroud)

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

# in module2.py
from module1 import class1
Run Code Online (Sandbox Code Playgroud)

这适用于python 2和3.

  • 我也认为这应该得到更多的选票。把它放在每个`__init__.py` 中基本上可以解决所有相关的导入错误。 (12认同)
  • @ErelSegal-Halevi 我确认一个缺点是如果您有来自不同模块的两个具有相同名称的文件。运行 python -m pytest 时,我遇到了这个冲突问题。如果作者能提供一个解决方案就太好了。 (7认同)
  • @pianoJames 我知道你的意思,这个(看起来,经过大量的麻烦之后)魔术修复似乎有点太简单了。但它有效。有兴趣不知道这是否有负面影响。 (5认同)
  • 如果你在两个不同的包中有两个同名的模块,会不会导致冲突? (3认同)
  • 我不能代表别人,但是我倾向于避免修改sys.path,因为我担心它可能会影响其他代码。(部分原因是因为我不知道它如何工作的复杂性。) (2认同)
  • 这不是相对导入。这是一个全局导入,恰好解析为相关文件。 (2认同)

Mar*_*ers 51

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

这意味着您正在将包内的模块作为脚本运行。在包内混合脚本是很棘手的,如果可能的话应该避免。使用导入包并运行您的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.pyscript.py可以使用from packagename import ...

  • 作为后备方案,您可以使用-m命令行开关告诉 Python 导入模块并将其用作文件__main__。但是,这不适用于 shebang 行,因为不再有脚本文件。

    如果您使用python -m foo.barfoo/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". 它只是另一个全局变量,就像__file__and一样__name__,由 Python 在导入时设置。

sys.path

上述所有内容都要求您的包可以导入,这意味着它需要在 .zip 中列出的目录(或 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.PathobjectHERE = Path(__file__).resolve().parent是对文件所在目录的引用,作为绝对路径)。


小智 32

我遇到了这个问题.黑客解决方法是通过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()
Run Code Online (Sandbox Code Playgroud)

  • 这不是一个很好的解决方案.还有,除了:`是坏的.使用`除了ImportError:`而不是! (26认同)
  • 这不是一个可怕的想法,但最好是检测使用哪个导入而不是尝试/除外.类似于`if __name__ =='__ main__':来自mymod import as_int; else:from .mymod import as_int`. (7认同)
  • 这里是`SystemError`.(Py 3.4) (5认同)
  • @pauljohn32如果您正在开发一个软件包,您应该通过使用可编辑的 pip install(来自“setup.py”所在的软件包根目录的“pip install -e .”)安装它来测试它,并将其作为软件包导入到您的测试文件,而不是像这样干预条件导入。 (2认同)

wol*_*evo 28

长话短说

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

概念澄清

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

原因是,简单来说,我们没有按照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
Run Code Online (Sandbox Code Playgroud)

跑吧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
Run Code Online (Sandbox Code Playgroud)

跑步python -m happy.me

happy当我们在包含、happy是包、是模块的目录中时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

Run Code Online (Sandbox Code Playgroud)

这个结构就是Python的习惯用法。main是我们的脚本,Python 中的最佳实践。终于,我们到了那里。


兄弟姐妹或祖父母

另一个常见的需求:

.
|-- happy
|   |-- happy_maker.py
|   `-- me.py
`-- sad
    `-- sad_maker.py
Run Code Online (Sandbox Code Playgroud)

我们想要导入sad_maker进来me.py,怎么做呢?

首先,我们需要将happysad放在同一个包中,因此我们必须上一级目录。然后from ..sad import sad_makerme.py.

就这些。


Gra*_*ell 16

对于 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(当前工作目录)的选项并将其设置为您在命令行上使用的相同内容 (2认同)
  • @gerardw PyCharm 根据项目目录的基本文件夹查看符号。如果它失败了,“通常”意味着您打开项目的位置有些奇怪。您可以尝试在不同的根目录中打开它。这就是西普里安所说的。可能对你有帮助 - 但也许没有 (2认同)

SeF*_*SeF 14

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
Run Code Online (Sandbox Code Playgroud)


fra*_*lau 12

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

import repackage
repackage.up()
from mypackage.mymodule import myfunction
Run Code Online (Sandbox Code Playgroud)

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


And*_*dor 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
...
Run Code Online (Sandbox Code Playgroud)

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

  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

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


Sam*_*mer 8

从同一目录导入

\n

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

\n

这是文件结构...

\n
Folder\n |\n \xe2\x94\x9c\xe2\x94\x80 Scripts\n |   \xe2\x94\x9c\xe2\x94\x80 module123.py\n |\n \xe2\x94\x9c\xe2\x94\x80 main.py\n \xe2\x94\x9c\xe2\x94\x80 script123.py\n
Run Code Online (Sandbox Code Playgroud)\n

这是main.py

\n
Folder\n |\n \xe2\x94\x9c\xe2\x94\x80 Scripts\n |   \xe2\x94\x9c\xe2\x94\x80 module123.py\n |\n \xe2\x94\x9c\xe2\x94\x80 main.py\n \xe2\x94\x9c\xe2\x94\x80 script123.py\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n

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

\n
\n

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

\n

从父目录导入

\n

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

\n

采取以下文件树...

\n
ParentDirectory\n \xe2\x94\x9c\xe2\x94\x80 Folder\n |   |\n |   \xe2\x94\x9c\xe2\x94\x80 Scripts\n |   |   \xe2\x94\x9c\xe2\x94\x80 module123.py\n |   |\n |   \xe2\x94\x9c\xe2\x94\x80 main.py\n |   \xe2\x94\x9c\xe2\x94\x80 script123.py\n |\n \xe2\x94\x9c\xe2\x94\x80 parentModule.py\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
from . import script123\nfrom Scripts import module123\n
Run Code Online (Sandbox Code Playgroud)\n


Árt*_*hur 7

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

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

project_demo/
??? main.py
??? some_package/
?   ??? __init__.py
?   ??? project_configs.py
??? test/
    ??? test_project_configs.py
Run Code Online (Sandbox Code Playgroud)

解决方案

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

from some_package import project_configs
Run Code Online (Sandbox Code Playgroud)


小智 6

希望这对那里的某人有价值-我浏览了六堆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'
Run Code Online (Sandbox Code Playgroud)

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

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

OK
Run Code Online (Sandbox Code Playgroud)

因此,如果有人尝试了一些建议,则认为他们的代码结构正确,并且如果您不将当前目录导出到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()
)
Run Code Online (Sandbox Code Playgroud)


小智 6

我尝试了上述所有方法均无济于事,才意识到-我的包名称中错误地包含了 a 。

-简而言之,就是所在目录下没有__init__.py。在发现如此愚蠢的事情后,我从未感到高兴。

  • 这听起来像是一个应该报告的错误。 (2认同)

Ant*_*kiy 6

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

import sys
from pathlib import Path
sys.path.append(str(Path(sys.argv[0]).absolute().parent.parent))
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你。

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

import sys
from pathlib import Path
sys.path.append(str(Path(sys.argv[0]).resolve().parent.parent))
Run Code Online (Sandbox Code Playgroud)


vij*_*j34 5

我收到了这个导入错误:尝试相对导入而没有已知的父包

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

from .filename import function
Run Code Online (Sandbox Code Playgroud)

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

from package_name.filename import function
Run Code Online (Sandbox Code Playgroud)

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