【问题标题】:Python 3 modules and package relative import doesn't work?Python 3 模块和包相对导入不起作用?
【发布时间】:2021-02-08 17:14:06
【问题描述】:

我在构建项目结构时遇到了一些困难。

这是我的项目目录结构:

MusicDownloader/
   __init__.py
   main.py
   util.py
   chart/
      __init__.py
      chart_crawler.py
   test/
      __init__.py
      test_chart_crawler.py

这些是代码:

1.main.py

from chart.chart_crawler import MelonChartCrawler

crawler = MelonChartCrawler()

2.test_chart_crawler.py

from ..chart.chart_crawler import MelonChartCrawler

def test_melon_chart_crawler():
  crawler = MelonChartCrawler()

3.chart_crawler.py

import sys
sys.path.append("/Users/Chois/Desktop/Programming/Project/WebScrape/MusicDownloader")
from .. import util

class MelonChartCrawler:
  def __init__(self):
    pass

4.util.py

def hi():
   print("hi")

在MusicDownloader中,当我通过python main.py执行main.py时,报错:

  File "main.py", line 1, in <module>
    from chart.chart_crawler import MelonChartCrawler
  File "/Users/Chois/Desktop/Programming/Project/WebScrape/MusicDownloader/chart/chart_crawler.py", line 4, in <module>
    from .. import util
ValueError: attempted relative import beyond top-level package

但是当我通过py.test test_chart_crawler.py在测试目录中执行我的测试代码时,它可以工作

当我第一次面对绝对、相对导入时,它看起来非常简单和直观。但它现在让我发疯。需要你的帮助。谢谢

【问题讨论】:

    标签: python python-3.x package pytest


    【解决方案1】:

    第一个问题是MusicDownloader 不是一个包。将__init__.pymain.py 一起添加到MusicDownloader,您的相对导入..chart 应该可以工作。相对导入仅在包内有效,因此您不能 .. 到非包文件夹。

    编辑我的帖子,为您的答案编辑提供更准确的答案。

    这一切都与__name__ 有关。相对导入使用它们所使用的模块的__name__from .(.) 部分来形成要导入的完整包/模块名称。用简单的术语解释进口商的__name__from 部分连接,用点表示要忽略/删除的名称组件,即:

    包含行:from .moduleB import something 的文件的__name__='packageA.packageB.moduleA' 导致导入packageA.packageB.moduleB 的组合值,因此大致为from packageA.packageB.moduleB import something(但不是绝对导入,因为如果直接这样输入的话)。

    包含行:from ..moduleC import something 的文件的__name__='packageA.packageB.moduleA' 导致导入packageA.moduleC 的组合值,因此大致为from packageA.moduleC import something(但不是绝对导入,因为如果直接这样输入)。

    这里是moduleB(C) 还是packageB(C) 并不重要。重要的是我们仍然拥有packageA 部分,它在这两种情况下都可以作为相对导入的“锚”。如果没有packageA 部分,则无法解析相对导入,我们将收到类似“尝试相对导入超出顶级包”之类的错误。

    这里还有一点需要注意的是,当一个模块运行时,它会获得一个特殊的 __name____main__,这显然会阻止它解决任何相关的导入问题。

    现在关于您的情况,请尝试将print(__name__) 添加为每个文件的第一行,并在不同的场景中运行您的文件,看看输出如何变化。

    也就是说,如果你直接运行你的 main.py,你会得到:

    __main__
    chart.chart_crawler
    Traceback (most recent call last):
      File "D:\MusicDownloader\main.py", line 2, in <module>
        from chart.chart_crawler import MelonChartCrawler
      File "D:\MusicDownloader\chart\chart_crawler.py", line 2, in <module>
        from .. import util
    ValueError: Attempted relative import beyond toplevel package
    

    这里发生的事情是...main.py 不知道 MusicDownloader 是一个包(即使在之前添加 __init__.py 的编辑之后)。在您的chart_crawler.py:__name__='chart.chart_crawler' 中,当使用from .. 运行相对导入时,包的组合值将需要删除两部分(每个点一个),如上所述,因此结果将变为''只是两个部分,没有封闭包装。这会导致异常。

    当您导入一个模块时,其中的代码会运行,因此它几乎与执行它相同,但不会将 __name__ 变为 __main__ 并且封闭包(如果有的话)不会被“注意”。

    因此,解决方案是将main.py 作为MusicDownloader 包的一部分导入。要完成上述操作,请使用以下代码在与 MusicDownloader 文件夹相同的层次结构级别上创建一个名为 launcher.py 的模块(靠近它,而不是靠近 main.py):

    print(__name__)
    from MusicDownloader import main
    

    现在运行launcher.py 并查看更改。输出:

    __main__
    MusicDownloader.main
    MusicDownloader.chart.chart_crawler
    MusicDownloader.util
    

    这里的__main____name__ 里面的launcher.py。在chart_crawler.py:__name__='MusicDownloader.chart.chart_crawler' 内部,当使用from .. 运行相对导入时,包的组合值将需要删除两部分(每个点一个),如上所述,因此结果将变为'MusicDownloader',导入变为@ 987654373@。正如我们在下一行看到的那样,当util.py 成功导入时,它会打印出__name__='MusicDownloader.util'

    差不多就是这样 - “这就是__name__”。

    附:没有提到的一件事是为什么带有test 包的部分有效。它不是以普通方式启动的,您使用了一些额外的模块/程序来启动它,并且它可能以某种方式导入它,所以它起作用了。要了解这一点,最好看看该程序是如何工作的。

    official docs中有一条注释:

    请注意,相对导入基于当前模块的名称。由于主模块的名称始终为“__main__”,因此用作 Python 应用程序主模块的模块必须始终使用绝对导入。

    【讨论】:

    • 只需添加 __init__.py 即可!顺便说一句,当我在我的test_chart_crawler.pyprint(sys.path) 时,PYTHONPATH 不仅有test 目录,还有MusicDownloader 目录。它只有MusicDownloader的父目录...
    • 很高兴我能帮上忙。 PYTHONPATH 是动态计算的,因此有一些因素会影响它,包括考虑绝对路径的代码所在的位置。
    • 嘿,我又遇到了同样的问题。我编辑了我的帖子...我不知道相对导入的原理是什么...T _ T
    • 非常感谢您详细的回答和善意。非常感谢。但是如果我按照你的指示(添加launcher.py),1.我必须更正main.py中的代码:从from chart.chart_crawler import MelonChartCrawlerfrom .chart.chart_crawler import MelonChartCrawler(添加点)2.项目文件夹结构不好。有两个启动文件.. launcher.pymain.py 有关 __name__ 的信息确实很有帮助,但我必须查找一些简单的示例开源项目或书籍。有知道的可以推荐一下吗?
    • 解决了这两个问题后,我会选择你的答案。再次感谢。
    猜你喜欢
    • 2015-04-30
    • 2022-06-15
    • 1970-01-01
    • 2016-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-01
    • 2013-05-14
    相关资源
    最近更新 更多