文章目录
Python中的模块和包
python高级编程语言的运行方式是利用解释器逐句运行指令,所以当我们运行一个Python程序时,系统会根据你提供的路径找到python.exe解释器,然后运行python程序。一旦执行完成,解释器就会关闭,而当你再次运行时又重新开始。
每一次解释器的退出都会把之前运行的Python程序中定义的变量和函数从内存中清除。
模块的概念和分类
什么是模块?
开发一个应用,很多时候会把代码写在多个python文件中,而且需要相互调用其中定义的变量和方法。为此python把每一个Python文件称为一个模块。
即:模块就是一个包含所有你定义的函数和变量的python文件,其后缀名为.py。
模块之间可以相互调用。
模块的分类
python自身提供的模块,一般称为内置模块或者标准模块,自己书写的称为自定义模块,第三方开发的模块称为第三方模块,所以python中的模块大概就分为三类:
- 标准模块
- 第三方模块
- 自定义模块
这里需要知道的是除了标准模块是可以直接使用的以外,其他两类模块的使用都有一定的限制。
模块的操作
怎么查看模块
查看模块调用路径
import sys
t = sys.path
print(t)
运行结果:返回系统路径列表
比如:我的python是安装在D:\python3\中,用户名是XXXXXX,得到就是下边这个结果,因人而异。
['D:\\python3\\python_doamon', 'D:\\python3\\python310.zip', 'D:\\python3\\DLLs', 'D:\\python3\\lib', 'D:\\python3', 'C:\\Users\\XXXXXX\\AppData\\Roaming\\Python\\Python310\\site-packages', 'D:\\python3\\lib\\site-packages', 'D:\\python3\\lib\\site-packages\\win32', 'D:\\python3\\lib\\site-packages\\win32\\lib', 'D:\\python3\\lib\\site-packages\\Pythonwin']
查看当前安装的第三方模块
第三方模块安装路径好像是默认的,与python的安装路径有关。
pip list # 列出已有的第三方模块,我的运行结果里是没有标准模块的。
单个模块的情况可以用:
# 查看单个第三方模块基本情况,版本信息、作者、存放位置等
pip show openpyxl
# 执行结果
Name: openpyxl
Version: 3.0.10
Summary: A Python library to read/write Excel 2010 xlsx/xlsm files
Home-page: https://openpyxl.readthedocs.io
Author: See AUTHORS
Author-email: charlie.clark@clark-consulting.eu
License: MIT
Location: d:\python3\lib\site-packages
Requires: et_xmlfile
Required-by:
自定义模块
一般自定义模块都是放在工作目录里,这样比较方便。
如果需要放其他的地方,好像就需要自己做api配置了……不是很清楚
标准模块的查看
好像没什么好的方法,反正可以直接用,具体的用法参考官方标准库文档:
3.10.12 Documentation (python.org)
Python 标准库 — Python 3.10.12 文档
Python 文档目录 — Python 3.10.12 文档
模块调用的基本语法
基本语法有两个:
语法一:
import module [as identifier] (, module [as identifier])
import 模块名 [as 别名]
基本的 import 语句(不带 from
子句)会分两步执行:
- 查找一个模块,如果有必要还会加载并初始化模块。
- 在局部命名空间中为
import
语句发生位置所处的作用域定义一个或多个名称。
当语句包含多个子句(由逗号分隔)时这两个步骤将对每个子句分别执行,如同这些子句被分成独立的 import 语句一样。
如果成功获取到请求的模块,则可以通过以下三种方式一之在局部命名空间中使用它:
- 模块名后使用
as
时,直接把as
后的名称与导入模块绑定。 - 如果没有指定其他名称,且被导入的模块为最高层级模块,则模块的名称将被绑定到局部命名空间作为对所导入模块的引用。
- 如果被导入的模块 不是 最高层级模块,则包含该模块的最高层级包的名称将被绑定到局部命名空间作为对该最高层级包的引用。 所导入的模块必须使用其完整限定名称来访问而不能直接访问。
模块内部是分层级的,类似定义函数时的镶嵌,
示例:假设有一个模块foo,其中最高级模块foo,下级有模块bar,再往下有模块baz
import foo # 此时获取的模块为最高层级模块foo,这时foo bound locally(foo绑定到局部空间)
import foo.bar.baz # 此时获取的模块为三个:foo、foo.bar、foo.bar.baz,但绑定到局部空间的只有foo
# 上两句指令中,“.”被称为前缀点号,作用是用来对层级分割的:
# foo.bar的意思就是bar在foo的下一层级
import foo.bar.baz as fbb # 此时获取模块三个,把foo.bar.baz绑定给了fbb
使用import语法获取模块时,除去用as指定绑定的标识符以外,想要调用其中最高层级以下的函数和变量时,都要用前缀点号指明其在当前层级机构中的位置。
示例:假设在之前foo.bar.baz层级有一个函数或变量function要使用,那么
import foo.bar.baz
foo.bar.baz.function() # 调用函数时候,得写全所在层级。
# 如果使用了as,则
import foo.bar.baz as baz
baz.function() # 因为此时foo.bar.baz绑定给了baz
访问模块中的函数或变量:模块名.函数名|变量。
语法二:
from relative_module import identifier [as identifier](, identifier [as identifier])
for 模块/包 import 函数/模块 [as 别名]
from
形式使用的过程略微繁复一些:
- 查找
from
子句中指定的模块,如有必要还会加载并初始化模块; - 对于
import
子句中指定的每个标识符:- 检查被导入模块是否有该名称的属性
- 如果没有,尝试导入具有该名称的子模块,然后再次检查被导入模块是否有该属性
- 如果未找到该属性,则引发
ImportError
。 - 否则的话,将对该值的引用存入局部命名空间,如果有
as
子句则使用其指定的名称,否则使用该属性的名称
from语法获取模块的方式过程与import有区别,其用法基本一致:
在from…import中,import后可以指定需要的模块或者函数,单独import是不能指定到模块中的函数和变量的。
示例:仍使用前边的假设,有一个模块foo,其包含的模块bar.baz中有一个函数fun。这时候:
from foo.bar.baz import fun # 获取指定模块foo.bar.baz中的函数fun
fun() # 调用函数
# 也可以指定调用模块
# 让foo.bar.baz绑定为baz
from foo.bar import baz # 这句大概等同于 "import foo.bar.bar as bar"
bar.fun() # 调用模块中的fun函数
获取多个模块,或模块中的函数和变量时,可以在对应位置用”,“隔开
from math import sqrt, pi # 同时获取了sqrt()和变量pi
t = sqrt(3)
print(t)
print(pi)
运行结果:
1.7320508075688772
3.141592653589793
通配符”*“的使用
from 模块名 import *
如果使用了通配符星号 ('*'
),则在模块中定义的全部公有名称都将按 import
语句所在的作用域被绑定到局部命名空间。
一个模块所定义的 公有名称 是由在模块的命名空间中检测一个名为 __all__
的变量来确定的;如果有定义,它必须是一个字符串列表,其中的项为该模块所定义或导入的名称。 在 __all__
中所给出的名称都会被视为公有并且应当存在。 如果 __all__
没有被定义,则公有名称的集合将包含在模块的命名空间中找到的所有不以下划线字符 ('_'
) 打头的名称。 __all__
应当包括整个公有 API。 它的目标是避免意外地导出不属于 API 的一部分的项(例如在模块内部被导入和使用的库模块)。
另外,通配符形式的导入 — from module import *
— 仅在模块层级上被允许。 尝试在类或函数定义中使用它将引发 SyntaxError
。
包的概念
Python 只有一种模块对象类型,所有模块都属于该类型,无论模块是用 Python、C 还是别的语言实现。 为了帮助组织模块并提供名称层次结构,Python 还引入了 包的概念。
什么是包
我的理解是Python提供了一种方便对模块组织和管理的方式,其本质还是模块,是一种特殊的模块。
上图是一个第三方模块”docx"查看的情况,包在系统中是以文件夹也就是目录的形式存放的,模块是以.py文件的形式存放的。
所以大概的可以把包看作是文件系统中的目录,并把模块看成是目录中的文件。与文件系统一样,包通过层次结构进行组织,在包之内除了一般的模块,还可以有子包。
要注意的一个重点概念是所有包都是模块,但并非所有模块都是包。 或者换句话说,包只是一种特殊的模块。 特别地,任何具有 __path__
属性的模块都会被当作是包。
所有模块都有自己的名字。 子包名与其父包名会以点号分隔,与 Python 的标准属性访问语法一致。 看上图这里打开查看的是一个第三方的包 docx
,这个包中又有一个名为 docx.dml
的子包。
包的分类
Python 定义了两种类型的包,常规包和 命名空间包。
-
常规包
常规包是传统的包类型,它们在 Python 3.2 及之前就已存在。 常规包通常以一个包含
__init__.py
文件的目录形式实现。 当一个常规包被导入时,这个__init__.py
文件会隐式地被执行,它所定义的对象会被绑定到该包命名空间中的名称。__init__.py
文件可以包含与任何其他模块中所包含的 Python 代码相似的代码,Python 将在模块被导入时为其添加额外的属性。例如,以下文件系统布局定义了一个最高层级的
parent
包和三个子包:parent/ __init__.py one/ __init__.py two/ __init__.py three/ __init__.py
导入
parent.one
将隐式地执行parent/__init__.py
和parent/one/__init__.py
。 后续导入parent.two
或parent.three
则将分别执行parent/two/__init__.py
和parent/three/__init__.py
。 -
命名空间包(详细内容可以查看官方文档)
命名空间包是由多个 部分 构成的,每个部分为父包增加一个子包。 各个部分可能处于文件系统的不同位置。 部分也可能处于 zip 文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。 命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。
命名空间包的
__path__
属性不使用普通的列表。 而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的sys.path
发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。命名空间包没有
parent/__init__.py
文件。 实际上,在导入搜索期间可能找到多个parent
目录,每个都由不同的部分所提供。 因此parent/one
的物理位置不一定与parent/two
相邻。 在这种情况下,Python 将为顶级的parent
包创建一个命名空间包,无论是它本身还是它的某个子包被导入。
包的使用
前文已经介绍过,包其实是一种特殊的模块,它的获取和使用与模块相同,这里就不再重复叙述了。
包相对导入
相对导入使用前缀点号。 一个前缀点号表示相对导入从当前包开始。 两个或更多前缀点号表示对当前包的上级包的相对导入,第一个点号之后的每个点号代表一级。 例如,给定以下的包布局结构:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
不论是在 subpackage1/moduleX.py
还是 subpackage1/__init__.py
中,以下导入都是有效的:
from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo
绝对导入可以使用 import <>
或 from <> import <>
语法,但相对导入只能使用第二种形式;其中的原因在于:
import XXX.YYY.ZZZ
om .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from …subpackage1 import moduleY
from …subpackage2.moduleZ import eggs
from …moduleA import foo
绝对导入可以使用 `import <>` 或 `from <> import <>` 语法,但相对导入只能使用第二种形式;其中的原因在于:
import XXX.YYY.ZZZ
应当提供 `XXX.YYY.ZZZ` 作为可用表达式,但 .moduleY 不是一个有效的表达式。