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 安装和管理。标准库有 mathossys 等。第三方库有 requestsnumpypandas 等。

  • 项目(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 会按照以下顺序查找模块:

    1. 当前工作目录(os.getcwd())。

    2. PYTHONPATH 环境变量。

    3. 安装的 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 packageAttributeError: module 'package' has no attribute 'module1' )。

    作者:脊柱蛀虫

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python相对路径导入详解与解决ImportError的方法指南:解决相对导入无已知父包报错。

    发表回复