Python 3模块和包相对导入不起作用?

时间:2016-01-31 05:27:31

标签: python python-3.x package pytest

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

这是我的项目目录结构:

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/WebScrap/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/WebScrap/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的测试目录中执行我的测试代码时,它可以正常工作

当我第一次面对绝对的相对进口时,它似乎非常简单直观。但它现在让我发疯了。需要你的帮助。谢谢

1 个答案:

答案 0 :(得分:2)

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

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

关于__name__的全部内容。相对导入使用它们所用模块的__name__from .(.)部分来形成要导入的完整包/模块名称。用简单的术语解释导入器的__name__from部分连接在一起,用点表示要忽略/删除的名称组件数,即:

包含行__name__='packageA.packageB.moduleA'的文件的

from .moduleB import something会导致导入packageA.packageB.moduleB的组合值,因此大致为from packageA.packageB.moduleB import something(但不是绝对导入,如果键入的话)直接)。

包含行__name__='packageA.packageB.moduleA'的文件的

from ..moduleC import something会导致导入packageA.moduleC的组合值,因此大致为from packageA.moduleC import something(但不是绝对导入,如果键入的话)直接)。

此处如果是moduleB(C)packageB(C)并不重要。重要的是我们仍然有packageA部分作为两个案例中相对导入的“锚点”。如果没有packageA部分,则不会解析相对导入,并且我们将收到类似“尝试相对导入超出toplevel包”的错误。

此处还有一点需要注意,当模块运行时,它会获得__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包的一部分。要完成上述操作,请使用以下代码创建一个模块,名称为launcher.py,与MusicDownloader文件夹(靠近它,不在main.py附近)的层次结构相同。< / p>

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'的{​​1}}。正如我们在成功导入from MusicDownloader import util时在下一行看到的那样,它会打印出util.py

所以这就是它 - “它就是那个__name__='MusicDownloader.util'”。

P.S。没有提到的一件事是为什么__name__包的部分有效。它没有以通用的方式启动,你使用了一些额外的模块/程序来启动它,它可能以某种方式导入它,所以它工作。要理解这一点,最好看看该程序是如何运作的。

official docs中有一条说明:

  

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