导入错误:尝试使用未知的父包进行相对导入

123
我正在学习使用Python编程,但在从包中的模块导入时遇到了一些问题。我正在使用带有Python 3.8.2 64位的Visual Studio Code。

我的项目目录:https://istack.dev59.com/ZQZhP.webp

.vscode
├── ecommerce
│   ├── __init__.py
│   ├── database.py
│   ├── products.py
│   └── payments
│       ├── __init__.py
│       ├── authorizenet.py
│       └── paypal.py
├── __init__.py
└── main.py

ecommerce/products.py 文件中,我有:

#products.py
from .database import Database
p = Database(3,2)

我希望能够从ecommerce/database.py文件中导入Database类,但我遇到了错误。

ImportError : Attempted relative import with no known parent package

1
你还应该指定运行模块的位置。 - dshri
8个回答

32
Python文档和实验中看来,相对导入(涉及.,..等)仅在以下情况下生效:
1. 导入模块具有除__main__之外的__name__; 2. 导入模块的__name__为pkg.module_name,即必须从目录层次结构的上层进行导入(以其父级pkg作为__name__的一部分)。
或者
导入模块是通过包含父包的模块语法指定的,如python -m pkg.module,此时其__name__仍为__main__,因此它将作为脚本运行,但相对导入将会生效。这里设置并使用了__package__来查找父包,而__name____main__更多信息请参见此处
[经过所有这些内容,似乎__package__sys.path是确定相对导入是否以及如何工作的关键。 __name__表示脚本或模块(即__main__module_name)。__package__指示与哪个包相对导入关联,__package__的顶部需要位于sys.path中。]
因此,以@AmitTendulkar的示例为例,如果在项目根目录下运行> python main.py> python -m main> python -m ecommerce.products,或从该根目录输入交互式python并导入mainimport ecommerce.products,则products.py中的相对导入将起作用。
但是,如果您在ecommerce目录内执行> python products.py> python -m products,或者从该ecommerce目录输入交互式python并导入products,则它们将失败。
添加以下内容会很有帮助:
print("In module products __package__, __name__ ==", __package__, __name__)

在每个文件中添加 etc. 以进行调试。

更新:

导入的工作方式取决于 sys.path__package__,而不是 __name__。 从 /home/jj 发出的 > python sub/mod.py 具有 sys.path__package__/home/jj/subNone - 在 sys.path 中的模块的绝对导入工作正常,相对导入失败。

> python -m sub.mod 具有 sys.path__package__/home/jjsub - 在 sys.path 中的模块的绝对导入工作正常,相对导入相对于 sys.path + __package__ 工作。

更有帮助的是添加

import sys    
print("In module products sys.path[0], __package__ ==", sys.path[0], __package__)

在每个文件中等等以进行调试。


1
实际上,我发现我需要完成以下所有步骤:1)在我的代码顶层目录中放置一个__init__.py文件;2)将顶层目录的父目录添加到我的PYTHONPATH中;3)在我的Python程序中设置__package__变量为包含__init__.py的目录名称。 - AstroFloyd
1
听起来很像在“更多信息”链接下“建议更改”的第四段中描述的样板代码。如果你通过> python code.py调用它,那么通过> python -m dir.code调用是否可以消除我在我的更新中提到的第2)和第3)点的需要? - Don Slowik
有点意思;我需要稍微调整一下相对导入,看起来运行成功后,我得到了 python3: Error while finding module specification for 'dir.code.py' (ModuleNotFoundError: __path__ attribute not found on 'dir.code' while trying to find 'dir.code.py') 的错误提示。 - AstroFloyd
以上的第一点对我非常有用(导入模块必须具有除__main__之外的__name__)。我在一个本来“正确”布局的包中遇到了这个导入问题,但没有意识到将模块作为脚本执行会搞乱本地导入。谢谢! - blthayer

23

由于你正在使用Python 3.8版本,因此导入模块的方式略有不同,但我认为这应该可以工作:

使用以下任一方法:

from database import Database
#Database is the class

或者尝试:

import database.Database

最后,这个选项非常安全且可能是最佳实践:

from . import Database  
# The '.' (dot) means from within the same directory as this __init__.py module grab the Database class.

9
“the imports work a little differently”的具体方式是什么?我无法复现操作者(OP)的问题(导入工作正常),而你的解决方案并不起作用,导致出现“ModuleNotFoundError”,尽管我正在使用Python 3.8.3。我看漏了什么吗?这篇文章让我在尝试理解导入系统时感到困惑。顺便提一下,我的__init__.py文件是空的。 - Jupiter
3
https://docs.python.org/3/reference/import.html#package-relative-imports 显示了OP所尝试做的应该可以工作。 - Jupiter
7
@Jupiter,这应该可以工作,但在3.8.5上对我来说不起作用。有趣的是,在VSCode中运行的Pylance可以解决最后两个代码片段中的导入问题,因此我可以获得代码完成提示,但是当我尝试运行代码时,它会抱怨“ImportError:attempted relative import with no known parent package”。只有第一个代码片段对我有效,但然后Pylance会失败。 - Arseny
44
为什么在Python社区中没有这个问题的“正确”答案? - Beyhan Gul
2
@BeyhanGul,Python 的导入方式非常复杂,而且随着时间的推移变得越来越糟糕。此时,C中的 #include 更好... - SO_fix_the_vote_sorting_bug
显示剩余10条评论

16

我曾在Windows上遇到类似的问题,以下方法对我有帮助(将目录适应为您的目录):

# local imports
import sys
sys.path.append(r"C:\path\to\your\project")

from ecommerce.database import Database

3
这似乎是一种不太正规的方法,但它能够工作并且最合理。 - Tanner Davis
@TannerDavis 我同意这不是最便携的,但解决了OP的问题。当我们开发的环境与脚本运行的环境显着不同时,我不得不使用它,因此必须硬编码位置。为了可移植性,也许pathlib更好,声明根文件夹并附加路径,使用类似于这样的东西: root_folder = r'{}'.format(pathlib.Path(pathlib.Path(__file__).parent.absolute().parent)) 在这里,您将不得不弄清楚您的模块确切位置。 - PB1899
这对我有效,Ubuntu 18,Python 3.8.1。 - Tiana987642
2
更加简单并且适用于我:sys.path.append(os.getcwd()),Python 3.7.6。 - Oliver
这在 Python 3.8.10 中对我没有起作用。 - Your Code Made Me Suicidal

8

考虑以下基本文件结构

├── ecommerce
│   ├── __init__.py
│   ├── database.py
|   └── products.py
└── main.py

我假设您正在项目根目录中运行python main.py

以下文本摘自Python教程,用于解释相对导入的基本规则:

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

因此以下代码将起作用:

# main.py
import ecommerce.products 
# or to use it directly 
# from ecommerce.products import my_product

ecommerce.products.my_product()

您的product.py代码可能如下所示:
# ecommerce/products.py
from .database import Database

def my_product():
    p = Database(3, 2)

而 database.py 的代码将如下所示:

# ecommerce/database.py

class Database():
    def __init__(self, x, y):
        self._x = x
        self._y = y
        print('Inside DB init')

    # Rest of the methods...

您现在将会得到:

> python main.py
Inside DB init

理想情况下,根目录的__init__.py文件是不必要的,因为包名从ecommerce开始。
您也可以运行python -m ecommerce.products命令直接调用products.py文件。但是,由于我们没有调用my_product()函数(只是定义了它),所以不会产生任何输出。
调用python ecommerce/products.py将不起作用,因为当前模块的名称将变为__main__而不是ecommerce。相对导入只在当前包中使用时有效(因此在主脚本中始终需要导入ecommerce包)。

2
为了允许使用相对导入,你需要将你的代码"转换成一个包"。这意味着需要所有以下步骤:1)在你的代码顶层目录(例如.vscode)放置一个__init__.py文件;2)将顶层目录(即你的.vscode所在的上级目录)的完整(绝对)路径添加到你的PYTHONPATH中;3) 在你的Python程序中将__package__变量设置为包含__init__.py的目录名称,在你的情况下是 ".vscode"。
然后你就可以在ecommerce/products.py中使用相对导入了。
from .ecommerce.database import Database

我不确定为什么名称中有一个点.vscode - 它是目录名的一部分还是您的目录树中的行的一部分?如果是后者,请在上面将.vscode替换为vscode


0

试试这个...

from ecommerce.database import Database

-1

这可能看起来像是一个hack,但它实际上是有效的,这是由于不良的包结构导致的

在你的情况下,请尝试导入

from .vs_code.ecommerce import database

现在无论您想在哪里调用类构造函数,都可以这样做:

database.Database()

你可以将其分配给一个变量并使用它。


-2

假设您将文件“products.py”变为可执行文件,这违反了包的概念,包不再是包,相对导入也无法工作。您必须在包外调用它。

在这里,我也不确定根目录名称是否可以以点号“.”开头,例如“.vscode”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接