构建项目结构时遇到一些困难。
这是我的项目目录结构:
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
的测试目录中执行我的测试代码时,它可以正常工作
当我第一次面对绝对的相对进口时,它似乎非常简单直观。但它现在让我发疯了。需要你的帮助。谢谢
答案 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应用程序主模块的模块必须始终使用绝对导入。