Python中模块的搜索路径及其手动添加——解决ImportError: attempted relative import with no known parent package

本文详细解析了在Python中遇到相对导入错误的原因,指出当模块作为主程序执行时,由于缺少父包信息导致相对导入失败。解决方案包括不直接执行模块,使用绝对导入,以及通过添加sys.path或修改PYTHONPATH来解决导入问题。同时,介绍了Python模块搜索路径和sys.path的重要性。
摘要由CSDN通过智能技术生成

一、出错场景

如果当前的工作路径就是在package1中(在此目录下执行a.py),而且在a.py中尝试用相对导入的方式引入b文件,就会导致出错。

|package1
    -__init__.py
    -a.py
    -b.py
#a.py
from .b import *

二、出错原因

正如错误提示所将的一样,这是由于“在不知道父包的情况下尝试相对导入”导致出错。

我们知道,python中是允许进行相对导入的,即通过.或者..引入其相应的模块。但是这种相对导入机制的原则是:

相对导入是使用模块的__name__属性来确定该模块在包层次结构中的位置

上面的模块是指要导入其他模块的模块,如上面的a.py模块。

当我们在package1所在路径下直接执行a.py时,我们可将a的__name__属性进行输出,结果为"main"(如下所示)。

#a.py
print(__name___)
from .b import *
输出:
"__main__"
"ImportError: attempted relative import with no known parent package"

由于此时模块a的__name__属性不包含任何包信息(它被设置为’main’),此时解析器认为模块a是顶级模块,其无法得知模块a的父包的信息,我们使用.或..就会导致出错。

那正常情况下使用相对导入时模块a的__name__属性是怎样的呢?比如当模块a不再是直接的执行文件时,当我们把调用模块a中函数的语句写在package1外面的test.py时,依旧在文件a中打印__name__变量。

|package1
    -__init__.py
    -a.py
    -b.py
 test.py
#test.py
from package1.a import main
main()
#a.py
print(__name___)
from .b import *
输出:
"package1.a"

此时我们可以看到正常情况下的父包信息(即“package1.a"), 由于有了父包的信息,解释器将"from .b import *“解析为”from package1.b import *”, 从而能够正常地在模块a中使用相对导入的方式导入模块b。

三、解决方案

1. 不直接将模块a作为执行文件

在上面的解释过程中我们已经得到了一种解决方案,即我们将在模块a中要执行的代码放到包外的文件test.py中, 从而使得不直接执行a.py而是执行test.py, 可以使模块a中的相对导入不报错。

|package1
    -__init__.py
    -a.py
    -b.py
 test.py

事实上,这也是在成熟的项目开发中使用的方式,即不会在包内直接执行文件。但有些时候我们只是跑一些demo,希望能够即时测试,那么可以使用以下方式。

2. 使用绝对导入

将a.py的导入修改为以下格式,可避免出错。(该方法只适用于单层相对引入,如果时多层相对引入,还要使用下面的方法3)

#a.py
from b import *

此时模块a的__name__属性仍然是"main",但由于我们不使用相对导入,就不需要利用__name__属性查找父包信息并根据相对路径找到模块b,而是直接根据模块的搜索路径寻找模块b。

在这里简单介绍下模块的搜索路径有哪些及其优先级:

当我们导入一个模块b时,解释器首先搜索具有该名称的内置模块(Python语言的内置模块)。如果没有找到,它就会在变量sys.path给出的目录列表中搜索一个名为b.py的文件。sys.path(即系统路径)是由以下这些路径组成的:

1.包含输入脚本的目录(或者没有指定文件时的当前目录)。
2.PYTHONPATH(目录名列表,语法与shell变量PATH相同)。
3.用户安装的包所在的路径。

sys.path举例:

  1. 比如我们上述脚本文件a所在的目录package1/
  2. PYTHONPATH由用户执行文件时指定,比如我们运行a文件时,可以使用以下的命令指定PYTHONPATH,从而添加导入模块的搜索路径。
    PYTHONPATH=…/ python a.py
  3. 即我们安装的numpy、sklearn这些包的路径,由python自动维护

所以当解释器要寻找模块b时,它根据sys.path中的”package1/"可以顺利找到该模块,从而不会报错。

3. 添加sys.path或修改PYTHONPATH

(1)添加sys.path

根据2中的介绍我们知道模块导入时寻找路径有哪些。假设我们想要绝对引入的模块不在文件a所在的目录时,我们可采用添加sys.path的方式解决该错误。

|package1
    -__init__.py
    -a.py
    -b.py
|package2
    -__init__.py
    -c.py

比如当我们想要在a.py中使用绝对导入方式导入模块c时,我们无法通过a.py所在目录路径"package1/"找到模块c,这时我们可以通过sys.path.append()的方式添加模块c所在的目录路径,从而使得解释器能找到模块c,如下所示。

#a.py
import sys
sys.path.append('../')
from package2.c import *

"""或者:
sys.path.append('../package2/')
from c import *
"""

值得注意的是,我们添加的系统路径只在程序运行时有效,程序停止后恢复默认搜索路径。

(2)修改PYTHON PATH

另外我们也可以在执行bash命令时添加PYTHONPATH来增加系统路径, 可有效避免该错误,比如:

amind@desktiop:~/package1$ PYTHONPATH '../' python a.py

此时a.py中的内容应该为:

#a.py
from package2.c import *

四、参考资料

【1】https://www.python.org/dev/peps/pep-0328/
【2】https://docs.python.org/3/tutorial/modules.html?highlight=import#the-module-search-path
【3】https://blog.csdn.net/xiongchengluo1129/article/details/80453599

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值