关于导包的几种方式

文件夹结构

(base) t@t-virtual-machine:~/PycharmProjects/importLeaning$ tree -L 2
.
├── folder01
│   ├── in01.py
│   └── inMain01.py
├── outMain01.py
├── outMain02.py
├── package02
│   ├── in02.py
│   ├── __init__.py
│   └── inMain02.py
└── venv

1 常规情况(完整路径+文件名)

1.1 文件夹(folder)

  • in01.py

def fun_in01():
print("This is in01.py!")
print("The abs_path is: importLearning -> folder01 -> in01.py -> fun_in01()")
  • inMain01.py(与in01.py在同一个文件夹)

from in01 import fun_in01 # 正常
fun_in01()
  • outMain01.py(与in01.py不在同一文件夹)

from folder01.in01 import fun_in01 # 正常
fun_in01()

文件夹正常调用方法

  • 同文件夹内部调用(inMain01.py 调用 in01.py中的函数)

  • 不同文件夹之间调用(outMain01.py 调用 in01.py 中的函数)

1.2 包(package)

  • in02.py

def fun_in02():
print("This is in02.py!")
print("The abs_path is: importLearning -> package02 -> in02.py -> fun_in02()")
  • __init__.py

def fun_init():
print("This is __init__.py!")
print("The abs_path is: importLearning -> package02 -> __init__.py -> fun_init()")
  • inMain02.py(与in02.py, __init__.py 在同一个包)

from in02 import fun_in02 # 正常
fun_in02()
# --------------------------------------
from __init__ import fun_init # 正常
fun_init()
  • outMain02.py(与in02.py, __init__.py不在同一包)

from package02.in02 import fun_in02 # 正常
fun_in02()
# --------------------------------------
from package02.__init__ import fun_init # 正常
fun_init()

包正常调用方法

  • 同包内部调用(inMain02.py 调用 in02.py, __init__.py中的函数)

  • 不同包之间调用(outMain02.py 调用 in02.py, __init__.py 中的函数)

1.3 总结

在同一个包/文件夹时:

  • from 脚本名 import 函数名

不在同一个包/文件夹时:

  • from 包名[文件夹名].脚本名 import 函数名

2 常用情况(不完整的路径/文件名)

  • inMain02.py(调用__init__.py 在同一个包)

正常情况

# inMain02.py
from package02 import fun_init # 注意,此时直接用包名,没有.后缀
fun_init()

异常①

# inMain02.py
# ImportError: cannot import name 'fun_in02' from 'package02' (/home/t/PycharmProjects/importLeaning/package02/__init__.py)
from package02 import fun_in02
fun_in02()

2.1 解析

从异常①代码和正常情况对比,可以发现包名和包内的__init__.py有特殊联系:

  • 可以通过包名(省去.脚本名)的方式,即from 包名[文件夹名].脚本名import 函数名,导入包内__init__.py文件中的函数;

  • 但,不能通过这个方式导入其他.py文件中的函数。

异常 ②

# inMain02.py
from . import fun_init # ImportError: attempted relative import with no known parent package
fun_init()

异常 ③

# inMain02.py
from .__init__ import fun_init # ImportError: attempted relative import with no known parent package
fun_init()
  • outMain02.py(调用__init__.py 不在同一个包)

正常

# outMain02.py
from package02 import fun_init # 注意,此时直接用包名,没有.后缀
fun_init()

异常② -> 正常

# inMain02.py (异常②)
from . import fun_init # ImportError: attempted relative import with no known parent package
fun_init()
# outMain02.py
from package02 import inMain02

异常③ -> 正常

# inMain02.py (异常③)
from .__init__ import fun_init # ImportError: attempted relative import with no known parent package
fun_init()
# outMain02.py
from package02 import inMain02

2.2 解析

异常②,异常③提示的错误信息都是:no known parent package (没有已知的父包)
解决方法是:

  • 在文件(outMain02.py)中from package02 import inMain02这一句代码,

  • 将package02包导入了进来,

  • 而此包就是(__init__.py)所在的包。

★ 解决了(inMain02.py)文件中no known parent package的问题。

综合执行(正常):

outMain02.py

  • from package02 import inMain02

inMain02.py单独执行会报错异常② & 异常③

  • from .__init__ import fun_init异常② or from . import fun_init异常③

  • fun_init()

2.3 最后比较

# ---------------------------------------------------
# outMain02.py
# (异常)ImportError: cannot import name 'fun_in02' from 'package02' (/home/t/PycharmProjects/importLeaning/package02/__init__.py)
from package02 import fun_in02 # fun_in02是在in02.py文件中的
fun_in02()
# ---------------------------------------------------
# outMain02.py
# (正常)
from package02 import fun_init # fun_init是在__init__.py文件中的
fun_init()

3 探究__init__.py的特殊性

既然包与 __init__.py 之间有特殊性,那么具体特殊的地方在哪里?

代码全清空,只留下包package02, 及其内部文件.py文件,包括__init__.py, in02.py , inMain02.py .

3.1 各个文件初始内容

  • __init__.py

def fun_init():
print("This is __init__.py!")
print("The abs_path is: importLearning -> package02 -> __init__.py -> fun_init()")
  • in02.py

def fun_in02():
print("This is in02.py!")
print("The abs_path is: importLearning -> package02 -> in02.py -> fun_in02()")
  • inMain02.py

from package02.__init__ import fun_init # 正常
fun_init()
from package02 import fun_init # 正常(<-测试1)
fun_init()
from . import fun_init # 异常②
fun_init()
from .__init__ import fun_init # 异常③
fun_init()

3.2 测试1

  • 原版 inMain02.py

from package02 import fun_init # 正常(<-测试1)
fun_init()
  • 改版1 - 报错

# inMain02.py
# ImportError: cannot import name 'fun_in02' from 'package02' (/home/t/PycharmProjects/importLeaning/package02/__init__.py)
from package02 import fun_in02
fun_in02()
  • 改版2

# --------------------------------------------------
# inMain02.py
# 改版1原代码,不动
from package02 import fun_in02
fun_in02()
# --------------------------------------------------
# __init__.py
# 修改文件__init__.py,即可配合改版2 - inMain02.py 实现正常输出
from .in02 import fun_in02 # 修改地方:增加此句
def fun_init():
print("This is __init__.py!")
print("The abs_path is: importLearning -> package02 -> __init__.py -> fun_init()")

3.3 分析

文件结构为: 包package02, 及其内部文件.py文件,包括__init__.py, in02.py , inMain02.py .

在__init__.py中,将同级文件in02.py中的函数导入,即可调用该函数。

3.4 测试2

  • 改版3

# inMain02.py
# 改版2的 __init__.py原代码,不动;重新修改inMain02.py,也可实现正常输出
import package02
package02.fun_in02()

3.5 总结正常输出的两种情况

# [通用] in02.py
def fun_in02():
print("This is in02.py!")
print("The abs_path is: importLearning -> package02 -> in02.py -> fun_in02()")
# [通用] __init__.py
from .in02 import fun_in02 # 修改地方:增加此句
def fun_init():
print("This is __init__.py!")
print("The abs_path is: importLearning -> package02 -> __init__.py -> fun_init()")
# 第一种 inMain02.py
# from 包名 导入 函数名,即 导入包内的__init__.py文件添加的函数
from package02 import fun_in02
fun_in02()
# 第二种 inMain02.py
# import 包名,即 导入包内的__init__.py文件
import package02
package02.fun_in02()

4 总结

  • __init__.py文件,具有特殊性,可以通过 from 包名 import 函数名 进行调用;

  • .py文件若含有from . import 导包或导入函数的情况,无法在该文件下正常执行,而父模块被外部加载时可以正常被使用。

  • 重点:通过操作__init__.py文件,即,在其内部导入包,可以给包本身增加attribute.


  • 三种import的方式

    • import 包名,实际上是import包内的__init__.py

      • import package02,就是把package02包中的__init__.py包含的内容导入,所以,此时__init__.py内部应该已经添加了待调用的函数。

    • from 脚本名(.py文件名) import 函数名

      • e.g., from package02.in02 import fun_in02

      • e.g., from .in02 import fun_in02 (被导入的脚本和目标脚本在同一文件夹,可省略包名)

    • from 包名 import 函数名和第二种相同,只是此时的脚本是__init__.py

      • from package02 import fun_in02

目录

注释:以上内容为本人自学过程的记录,可能有所欠缺或错误,欢迎随时联系,共同探讨。