Python相对路径导入详解与解决ImportError的方法指南:解决相对导入无已知父包报错。
本文全文较长,最重要的已经放在前面了,剩余部分按需观看。
0. 总结
相对导入只适合包内模块去使用!!!
一个 .py 文件一旦使用相对导入,则直接运行该文件一定会报错:“ImportError: attempted relative import with no known parent package”。
正确的使用方法是:
包内的模块之间使用相对导入,包外的模块通过绝对路径导入去调用这个包中的模块。
【包内相对导入,包外绝对导入】
示例:
假设现有如下文件结构,main.py 与 package 在同一级目录。package 是一个文件夹(包),其中包含了模块 module1.py 和 module2.py 以及包的初始化文件__init__.py。即,main.py在包外,module1.py在包内。
main.py
package/
├── __init__.py
├── module1.py
└── module2.py
包内相对导入:
现在假设 package 内的模块 module1 相对导入了同级目录的模块 module2 的函数 func2 。
module1.py :
# module1.py
from .module2 import func2
包外间接调用:
main.py :
# main.py
from package.module1 import func2
func2()
module2.py:
# module2.py
def func2():
print("func2")
则运行 main.py 的输出结果是:
func2
如果你不知道什么是文件夹和包的区别,不知道什么是相对导入和绝对导入,以下是一些补充信息。
1. 基础概念辨析:模块、包、文件夹的区别
文件夹/目录(Folder/Directory):一个操作系统级别的概念,用于存储文件和子文件夹。
脚本(Script):单个 .py
文件,可以包含函数、类等定义,但主要目的是执行一些操作。
模块(Module):单个 .py
文件,可以包含函数、类、变量等定义,用于实现特定的功能。主要用于代码复用和组织,可以被其他脚本或模块导入使用。
程序(Program):一个或多个脚本或模块组成的一个整体,用于完成一个完整的任务或功能。程序可以是一个简单的脚本,也可以是一个复杂的项目。通常会通过主程序 main.py
启动,主程序负责协调各个模块的运行。
包(Package):一个文件夹/目录,但是目录必须中包含一个特殊的文件 __init__.py
。包可以包含子包,形成层次结构。
库(Library):一组模块或包的集合,通常用于实现特定的功能或功能集。库可以是标准库(Python 自带的库),也可以是第三方库(通过 pip
安装的库)。通常通过 import
语句导入使用。第三方库可以通过 pip
安装和管理。标准库有 math
、os
、sys
等。第三方库有 requests
、numpy
、pandas
等。
项目(Project):一个包含多个文件和目录的完整开发任务,用于实现一个完整应用程序。
2. 绝对路径和相对路径(Absolute & Relative)
2.1 使用方法
假设有一个包结构如下:
package/
├── __init__.py
├── module1.py
└── subpackage/
├── __init__.py
└── module2.py
在 module1
中绝对导入 module2
:
# module1.py
from package.subpackage import module2
module2.func2()
在 module2
中绝对导入 module1
:
# module2.py
from package import module1
module1.func1()
在 module1
中相对导入 module2
:
# module1.py
from .subpackage import module2
module2.func2()
在 module2
中相对导入 module1
:
# module2.py
from .. import module1
module1.func1()
2.2 使用场景
绝对导入的使用场景
跨包导入:当需要从一个包导入另一个包中的模块时,绝对导入是更好的选择。
清晰的路径:当模块结构复杂时,绝对路径更清晰,便于理解和维护。
避免冲突:在大型项目中,绝对路径可以避免不同模块之间的路径冲突。
相对导入的使用场景
包内部模块导入:在包内部的模块之间导入时,相对导入可以使代码更简洁。
包的位置可能变化:如果包的位置发生变化,相对导入可以减少路径调整的工作量。
2.3 注意事项
相对导入的限制
不能在脚本中使用:如果直接运行脚本(如 python script.py
),相对导入会报错。
仅适用于包内部:相对导入只能在包内部的模块之间使用,不能在包外部使用。
绝对导入的优点
路径明确:绝对路径更清晰,便于理解和维护。
避免冲突:在大型项目中,绝对路径可以避免不同模块之间的路径冲突。
适用范围广:适用于导入项目中的任何模块,无论当前模块的位置如何。
🤔总是实在不行就用绝对导入,只要你的文件位置不会变来变去,包不出错的。
3. 基础的 import 语法
3.1 导入方法1:导入整个模块
先导入整个模块,再通过模块名访问指定对象。 模块中的所有对象(函数、类、变量等)都可以通过 模块名.对象名
的方式访问。例如:
import 模块名
模块名.函数名()
优点:可以清楚地知道对象来自哪个模块 & 不会污染当前命名空间。
缺点: 前缀冗长。
3.2 导入方法2:导入模块中的所有对象
一口气导入模块中的所有对象(函数、类、变量等),模块中的所有对象都可以使用原本的名字直接调用:
from 模块名 import *
函数名()
优点:调用简洁。
缺点:不清楚哪些对象被导入了 & 可能命名冲突。
3.3 导入方法3:导入模块中的指定对象
将指定的对象导入到当前命名空间中,可以直接使用对象名调用,而不需要加上模块名作为前缀。
from 模块名 import 函数名, 对象2, ...
函数名()
优点:调用简洁。
缺点是:可能命名冲突。
3.4 导入方法4:别名导入
为模块设置一个别名,使用别名来访问模块中的对象。
为导入的对象设置别名,下面以函数对象为例。
import 模块名 as 模块别名
from 模块名 import 函数名 as 函数别名
模块别名.函数名()
函数别名()
这种方法主要用于解决名字过长和命名冲突问题。
4. 包的导入
4.1 导入包中的模块(当前文件与包在同一级目录)
如果模块位于一个包中,可以使用点号(.
)来指定路径。
示例: 假设当前文件结构如下:
main.py
package/
├── __init__.py
├── module1.py
└── subpackage/
├── __init__.py
└── module2.py
( __init__.py 文件不可以缺省!!🧐 不然你之后运行 main.py 会报错,例如:AttributeError: module 'package' has no attribute 'module1' )
随后,在 main.py 中,可以这样导入模块并使用(假设你在 module1 和 module2 中分别实现了函数 func1 和 func2 ):
# main.py
import package.module1
import package.subpackage.module2
package.module1.func1()
package.subpackage.module2.func2()
这样很麻烦对不对?前缀死长死长的。
4.2 导入包中的对象
可以使用绝对路径直接导入包中的对象:
# main.py
from package.module1 import func1
from package.subpackage.module2 import func2
func1()
func2()
但是这样导入,如果包的层次很多怎么办,那导入语句就会很长?所以接下来讲讲 __init__.py 怎么用,这才是包的作用!❤ 【本质其实有点像是超链接】搞完后面的操作你就可以像下面这样导入并调用包里的函数了:
# main.py
from package import func1, func2
func1()
func2()
⭐ 只需要在两个 __init__.py 文件中分别增加以下内容就可以了!
# package/__init__.py
from .module1 import func1
# package/subpackage/__init__.py
from .module2 import func2
如果你直接执行这些__init__.py文件,则会报错:ImportError: attempted relative import with no known parent package 这是正常的。
4.3 相对导入(仅适用于包内导入)
在包内部的模块之间可以使用相对导入。
示例: 在 module1.py
中导入 subpackage/module2.py
中的 func2
:
from .subpackage.module2 import func2
.
表示当前包。
..
表示上一级包。
注意:
相对导入只能在包内部使用,不能在包外部使用。
⭐直接运行使用相对导入的 .py 文件(脚本)会报错!!!!
5. 其它一些你可能需要的信息
5.1 命名冲突
如果导入的对象与当前命名空间中的对象同名,会导致冲突。可以通过别名解决。
示例:
from math import sqrt
from mymodule import sqrt as my_sqrt
print(sqrt(16)) # 使用 math 模块中的 sqrt
print(my_sqrt(16)) # 使用 mymodule 模块中的 sqrt
5.2 使用 import 语句导入模块时,python如何识别导入的模块时哪一个?模块查找路径
Python 会按照以下顺序查找模块:
-
当前工作目录(
os.getcwd()
)。 -
PYTHONPATH
环境变量。 -
安装的 Python 包和模块(标准库和第三方库)。
如果模块不在这些路径中,可以通过以下方式解决:
将模块目录添加到 sys.path
import sys
sys.path.append('/path/to/module/directory')
修改 PYTHONPATH
环境变量。
5.3 循环依赖
如果两个模块相互导入对方,可能会导致循环依赖问题。尽量避免这种情况,或者通过调整模块结构来解决。
ps:我之前没搞明白包和模块啊这些东西的概念,乱用一通,导致项目老是报错,特别是乱七八糟的导入引用,VSCode打开的文件夹层级变一下也会导致原本正常的程序报错。。。一开始我直接在浏览器搜,看了几篇文章感觉讲得乱七八糟的看不懂,最后还是求助KIMI老师,然后自己实践了一遍,终于是大概明白之前为啥老是报错了(就文中提到的ImportError: attempted relative import with no known parent package和AttributeError: module 'package' has no attribute 'module1' )。
作者:脊柱蛀虫