Python的模块(module)和包(package)

Python的模块(module)和包(package)概述

在Python中,简单地说,模块(Module)一般是单个python文件;包(package)由分层模块(Module)构成——相关目录里的模块构成。Python中的库(library)是参考其它编程语言的说法,既可以是一个模块也可以是一个包,换言之是对模块或包的通俗的说法。

模块(module)其实就是py文件【注1】,里面定义了一些函数、类、变量等。模块的名字就是该文件的名字(不包含后缀)。

【注1:Python 中的模块,往往对应于Python 程序文件。每一个文件都是一个模块。模块也可以是使用如 C、 Java或C#等其他语言编写的扩展包,甚至还可以是在包导入时的文件路径。一般情况下,是一个以.py为后缀的文件,其他可作为module的文件类型还".pyo"、".pyc"、".pyd"、".pyw"、".dll",但Python初学者几乎用不到。】

模块的分类【注2】:标准模块(库)、第三方模块(库)、自定义模块。

自定义模块就是你自己编写的模块,如自己定义的函数,需要经常调用时,就可以自己定义一个模块,将常用函数、类、变量等写入模块里,另外的模块若要使用,使用import语句导入该模块,就可以使用了。第三方模块的数量非常庞大,有许多非常有名并且影响广泛的模块。第三方模块需要安装(详情可见 https://blog.csdn.net/cnds123/article/details/104393385),再使用import语句导入后使用。标准模块(库)是安装Python时自动安装在Python安装路径的Lib文件夹中,不需要用户安装,可以直接使用import语句导入后使用

【注2;还有一个分类内置模块(built-in module),内置模块是用C写的,提供了对系统功能的访问。从Python的标准库路径下面是找不到的。虽然内置模块不是标准库,但是内置模块可以划分到标准库一类中去,这里需要注意的是划分归类,而不是本质相同。大多数情况下,对它们之间没有做区分的必要。但是在理解Python的模块查找顺序时,这却是一个主要的差异。https://www.cnblogs.com/pluse/p/8667864.html

包(package)是多个模块的聚合体形成的文件夹,里面可以是多个py文件,也可以嵌套文件夹。import 语句中的目录路径只能是以点号间隔的——子包名与其父包名会以点号分隔。

可以把包看成是文件系统中的目录,并把模块看成是目录中的文件。包通过层次结构进行组织,在包之内除了一般的模块,还可以有子包。

Python 定义了两种类型的包,即 常规包(regular packages) 和 命名空间包(namespace packages)。常规包是存在于 Python 3.2 及更早版本中的传统包。常规包即包含 __init__.py 文件的目录。当导入一个常规包时,__init__.py 文件被隐式执行,而且它定义的对象被绑定到包命名空间中的名称。 __init__.py 文件能包含其他任何模块能够包含的相同的 Python 代码,而且在导入时,Python 将给模块增加一些额外的属性。

从 Python 3.3 开始,Python 引入了 命名空间包 的概念。命名空间包是不同文件集的复合,每个文件集给父包贡献一个子包,所有的包中都不需要包含 __init__.py 文件。文件集可以存于文件系统的不同位置。文件集的查找包含导入过程中 Python 搜索的压缩文件,网络或者其他地方。命名空间包可以但也可以不与文件系统的对象直接对应。命名空间包提出的本意却不是说为了导入没有init.py 的文件夹的 Python 模块,而是利用命名空间包这个技术来导入目录分散的代码。

顺便提示:所有包都是模块,但并非所有模块都是包。换句话说,包只是一种特殊的模块。具体来说,任何包含__path__属性的模块都被视为包。

【官方文档:6. 模块 — Python 3.10.7 文档

5. 导入系统 — Python 3.10.7 文档

这两个概念,有助于我们更好地使用python进行模块化编程,通过模块化编程,我们能把大的工程拆分成子任务(子模块),好处是,可维护性好,问题也便于排查;编写好的模块复用性好,当需要重复实现时,再次调用即可,不必再重新编写了;每个模块都有单独的命名空间,避免发生一些例如变量、函数命名上的冲突。可用于python复杂程序的组织【https://blog.csdn.net/cnds123/article/details/108614392 】。

Python的模块、包的导入使用

import 语句有两种形式

基本的 import 语句(不带 from 子句)和 带 from 子句的import 语句。

基本的 import 语句(不带 from 子句)格式:

import 模块名 [as 别名]

对于包

import 包名.模块名 [as 别名]

其中,对于包中含包的包名之间英文句号分隔。

使用户程序(导人者)以一个整体获取一个模块,这种形式想使用模块或包中的成员(模块中的函数、类、变量)需要加前缀:“模块名.”、 “包名.模块名.”或 “别名.”

带 from 子句的import 语句格式:

from 模块名 import 成员名 [as 别名]

其中,成员名可以有多个,之间用英文逗号分隔

对于包

from 包名.模块名 import 成员名 [as 别名]

允许用户程序从一个模块文件中获取特定的名称 ,这种形式因已指出了成员名,这种形式想使用模块或包中的成员可以直接使用成员名无需加前缀,若为成员名指定了别名,可以直接使用别名无需加前缀。

导入包的方法与导入模块的方法类似,需要注意的只是要用点号语法指明路径层次——import 语句中的目录路径只能是以点号间隔。

【import语句的用法可参照https://www.jb51.net/article/238794.htm

补充说明

1)可以一次导入多个模块,多个模块之间用逗号隔开,如:import sys,os

2)在导入多个模块的同时,也可以为模块指定别名,如导入sys、os两个模块,并为sys指定别名s,为os指定别名o:

import sys as s,os as o

3)导入模块成员时,也可以为成员指定别名,如导入sys模块的argv成员,并为其指定别名v,这样就可以直接使用成员的别名访问:

from sys import argv as v

print(v[0])  #就相当于 print(argv[0])

3)可以 *代表多有成员,此方式不推荐使用

比如同时导入 module1 和 module2 内的所有成员:

from module1 import *

from module2 import *

假如这两个模块内都有一个 foo() 函数,那么当在程序中执行如下代码时:

foo()

上面调用的这个 foo() 函数到底是 module1 模块中的还是 module2 模块中的?因此,这种导入指定模块内所有成员的用法是有风险的。

4)import语句是最常用的导入机制,但并不是唯一的方式,importlib 模块以及内置的__import__() 方法都可以实现模块的导入。

import的搜索顺序

当import语句导入后,默认情况下,搜索模块的路径为:首先判断这个module是不是built-in module(内置模块,见前面【注2】),然后:

1)当前目录的路径

2)环境变量 PYTHONPATH 中指定的路径列表

3)Python 安装路径

可以利用sys.path查看当前的搜索路径

import sys

sys.path

说明,sys.path在python脚本执行时动态生成,一个由字符串组成的列表,用于指定模块的搜索路径。一般来说,第三方库会安装在site-packages路径下。列表的第一项 path[0] 若为空字符串,这将导致 Python 优先搜索当前目录【注3】中的模块。

【注3:当前正在使用的目录称为当前目录(或者工作目录)。执行程序的编译和运行时,执行对象所在的目录基本上就被视为当前目录。】

要修改import搜索路径有多种方式:

1)临时添加,即动态修改 sys.path,因为 sys.path 为 list,所以我们可以很轻松的操作 list 实现搜索路径的修改。这种方式只会对当前项目临时生效——只在执行当前文件的窗口中有效,窗口关闭后即失效。

import sys

sys.path.append('D:/demo/python_module')

其中,'D:/demo/python_module' 也可写为 'D:\\demo\\python_module'

Python代码使用的路径中若使用的 '\' 需要使用 \ 进行转义写为\\

2)修改 PYTHONPATH 环境变量,这种方式会永久生效,而且所有的 Python 项目都会受到影响,因为 Python 程序启动时会自动去读取该环境良好的值。

不同平台设置流程不尽相同,这里以windows10操作系统中为例:按WIN+R键,打开“运行”对话框,输入sysdm.cpl,按回车键也能打开“系统属性”对话框,“高级”选项卡中,单击“环境变量”按钮,单击系统变量中的“新建”按钮,在“变量名”文本框内输入 PYTHONPATH,表明将要建立名为 PYTHONPATH 的环境变量;在“变量值”文本框内输入D:\demo\python_module

然后点击“确定”。重启系统才能使之生效。这种方法添加的路径可以对安装的多个不同版本的Python都生效。

3)增加.pth 后缀的文件。在 sys.path 已有的某一个目录下添加.pth 后缀的配置文件,该文件的内容就是要添加的搜索路径,Python 在遍历已有目录的过程中,如果遇到.pth 文件,就会将其中的路径添加到 sys.path 中。

Python 在遍历已知的库文件目录过程中,如果见到一个 .pth 文件,就会将文件中所记录的路径加入到 sys.path 设置中,于是 .pth 文件所指明的库也就可以被 Python 运行环境找到了。这个文件虽然可以放在python的已知库文件的目录里面就可以了,不过我还是建议统一放到python安装目录的site-packages目录中。

主文件名是没有限制的,你可以根据自己的库内容进行定义,只要扩展名为.pth就可以了。文件内容一般只要设置一下路径就可以,可以是相对路径(相对于.pth所在的路径),也可以是绝对路径。如用记事本建立一个名为mypath.pth文件,添加内容(根据你实际需要写)如D:\demo\python_module,放到python安装目录的site-packages目录中。

4)将模块保存到指定位置

可以直接将我们已编写好的py 文件添加到Python安装路径中的lib\site-packages 路径下,就相当于为 Python 扩展了一个 模块,这样任何 Python 程序都可使用该模块。

使用模块(module)和包(package)的例子

下面给出一个简单而完整的示例项目,其目录结构如下

其中,call_test2.py 要导入使用first.py和abcd.py

first.py文件的内容如下:

a = 1.5
def myfun(s):
return(s + 1)

abcd.py文件的内容如下:

a = 20
def myfun(s):
    return(s*s)

 call_test2.py文件的内容如下:

import first
import folder1.abcd as second
print(first.a) #显示1.5
x=3
print(first.myfun(x)) #显示4
print("----------")
print(second.a) #显示20
y=5
print(second.myfun(5)) #显示25

运行call_test2.py,效果如下:

从此例中,可以明了感知,每个模块都有单独的命名空间,避免发生一些例如变量、函数命名上的冲突。

推荐阅读:

t​​​​​​​Python behind the scenes #11: how the Python import system works (tenthousandmeters.com)https://tenthousandmeters.com/blog/python-behind-the-scenes-11-how-the-python-import-system-works/

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐