Python 包、_ _init_ _.py文件

81 阅读4分钟

一、包的定义

什么是包?

随着模块数目的增多,把所有模块不加区分地放到一起是极不合理的,于是Python为我们提供了一种把模块组织到一起的方法,即创建一个包。

包就是一个包含有 _ _ init_ _.py文件==文件内容为空==)的文件夹,文件夹内可以组织子模块或子包。

例如:

Mypackage               # 顶级包
├── __init__.py     
├── Sonpackage          # 子包
│   ├── __init__.py
│   ├── Grandson1.py    # 子包的子模块
│   └── Grandson2.py    # 子包的子模块
└── me.py               # 子模块

包结构如图所示:在这里插入图片描述

我们也可以这么认为:==_ _init_ _.py==文件的作用是让一个呈结构化分布(以文件夹形式组织)的代码文件夹变成可以被导入(==import==)的模块包。

为何要用包?

包的本质是模块的一种形式,创建包的目的不是为了运行,包是用来被当做模块集合导入的。

使用包可以提高程序的结构性和可维护性。

==要注意:包下的_ _init_ _.py文件内容是空的,_ _init_ _.py文件标识当前文件夹是一个可导入的包,执行文件就可以通过import语句来导入包里的模块了。当然,我们也可以给_ _init_ _.py文件里写好import导入模块语句,因为当执行文件导入包的时候将还会自动运行包下的_ _init_ _.py文件,实现间接导入模块的效果。==

二、包的使用

在python3中,即使包下没有 _ _init_ _.py文件,import 包仍然不会报错。而在python2中,包下一定要有该文件,否则import 包会报错。

包的导入(导包就是在导包下的_ _init_ _.py文件)

包属于模块的一种,因而包以及包内的模块均是用来被导入使用的,而绝非被直接执行,首次导入包(如import Mypackage)同样会做三件事:

  1. 执行包下的 _ _init_ _.py文件
  2. 产生一个新的名称空间用于存放 _ _init_ _.py执行过程中产生的名字
  3. 在当前执行文件所在的名称空间中得到一个名字Mypackage,该名字指向 _ _init_ _.py的名称空间。也就是说==导入包时并不会导入包下所有的子模块与子包,而是只导入了_ _init_ _.py文件(即使这个文件是空内容)==。

我们可以给_ _init_ _.py文件里写好一系列的import导入模块语句,这样我们导入包时就相当于已经导入了所需的模块,因为导入包的第一步就会执行包下的_ _init_ _.py文件。

举例

_ _init_ _.py文件内容为空
若_ _init_ _.py内容为空时,执行文件就要这么写:
# import...导入:包名.子包名.模块名

import Mypackage.me  # 点号代表下级,比如这里的意思就是导入Mypackage包下的me模块
import Mypackage.Sonpackage.Grandson1
import Mypackage.Sonpackage.Grandson2

# 严格按照“包名.子包名(无子包则不写).模块名.功能”语法使用

Mypackage.me.f()
Mypackage.Sonpackage.Grandson1.f()
Mypackage.Sonpackage.Grandson2.f()
# from...import...导入:from 包名.子包名 import 模块名

from Mypackage import me
from Mypackage.Sonpackage import Grandson1
from Mypackage.Sonpackage import Grandson2

# 通过“模块名.功能”语法使用

me.f()
Grandson1.f()
Grandson2.f()
# 当然也可以这样直接精准导入功能
from Mypackage.Sonpackage.Grandson1 import f

f()
_ _init_ _.py文件内容不为空(在_ _init_ _.py文件中写入模块导入语句)
若在_ _init_ _.py文件里提前用import...写好模块导入语句:
import Mypackage.me
import Mypackage.Sonpackage.Grandson1
import Mypackage.Sonpackage.Grandson2

执行文件一键导入包后使用的时候就应该这么写:

import Mypackage

# 严格按照“包名.子包名.模块.功能”语法使用

Mypackage.me.f()
Mypackage.Sonpackage.Grandson1.f()
Mypackage.Sonpackage.Grandson2.f()
若在_ _init_ _.py文件里提前用from...import...写好模块导入语句:
from Mypackage import me
from Mypackage.Sonpackage import Grandson1
from Mypackage.Sonpackage import Grandson2

执行文件就可以一键导入包,使用的时候这么写:

import Mypackage

# 按照“包名.模块名.功能”使用(不管是几级目录下的模块,都这样用)

Mypackage.me.f()
Mypackage.Grandson1.f()
Mypackage.Grandson1.f()


# 通常我们这么写
import Mypackage as m

m.me.f()
m.Grandson1.f()
m.Grandson1.f()

强调

  1. 关于包相关的导入语句分为import...和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:==凡是在导入时带点的,点的左边都必须是一个包(使用点号精确描述层级关系),否则非法。==

    可以带有一连串的点,如import 顶级包 . 子包 . 子模块,但导入时都必须遵循这个原则。

    但对于导入后的使用情况,在使用时就没有这种限制了。

  2. 包A和包B下就算有同名模块也不会冲突,因为使用时要写包名,所以 A.a 与 B.a 是两码事。

三、包内模块互相导入

针对包内的模块之间互相导入,导入的方式有两种

  1. 绝对导入:以顶级包为起始(推荐)

    比如我们在_ _init_ _.py文件里以绝对导入的方式导入模块

    from Mypackage import me
    # 或者
    import Mypackage.me
    
  2. 相对导入:.(单点号)代表当前文件所在的目录,..(双点号)代表当前目录的上一级目录,依此类推,不过双点号多点号容易出错,所以还是推荐绝对导入的方式。

    比如我们在_ _init_ _.py文件里以相对导入的方式导入模块

    from .Sonpackage import Grandson1
    
    # 只使用import...则不能进行相对导入