模块和包
导入模块、包和构建项目目录
模块和包
1. 模块
用一砣代码实现了某个功能的代码集合
类似于 函数式编程 和 面向过程编程
- 函数式编程: 也叫 无副作用 编程,它不会改变外部变量
- 面向过程:函数的堆砌,不断调用函数来完成一个功能,提供了代码的 重用性 和 代码间的耦合
模块分为三种
自定义模块:自己写的模块
内置标准模块:又称标准库
开源模块
1.1 模块的调用
使用 import module
的形式,来引入模块
引入标准库
import os, sys import re
引入自定义模块
创建一个
test.py
文件import test # 通过如上形式,即可以引入自定义的模块,解释器通过搜索路径找到 test.py
引入第三方模块
下载第三方库
nmcli
$ pip install nmcli
直接引入
import nmcli
使用 from module import ...
的形式,来引入模块中所需的对象
from os import environ # 引入系统环境 变量
from test import add # 引入自定义 test 模块中 add 方法
from asyncio import Queue # 引入异步标准库的 队列类
若需要重命名,通常用于同名模块、方法、类等
from fibo import fib as fibonacci # 将 fibo 模块下的 fib方法 重命名为 fibonacci
1.2 脚本执行模块
假设有 fibo.py
文件
"""-- fibo.py --"""
def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
可以使用如下方式运行 Python 模块
$ python fibo.py <arguments>
# 如 python fibo.py 50
会直接视为将代码添加到模块末尾 if __name__ == '__main__':
下
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
通常用于快捷测试,也是程序执行的入口
if __name__ == '__main__':
__name__
的特性是在本模块值为'__main__'
,而在其他模块声明后,则是其他模块名- 通常用来模块内测试代码功能,使功能模块的功能部分,和逻辑执行分离
1.3 模块搜索路径
标准库: Python 自带的标准模块,内嵌到编译器中,提供操作系统等的基本调用接口,如 sys
对于非内嵌库(自定义),当模块导入后(如: import fibo
)
- 解释器首先搜索具有
fibo
名称的内置模块,sys.builtin_module_names
中 - 若没找到,则会在
sys.path
给出的目录列表中搜索
import sys
sys.path
## 输出结果
['C:\\ProgramData\\Miniconda3\\Scripts\\ipython.exe',
'c:\\programdata\\miniconda3\\python39.zip',
'c:\\programdata\\miniconda3\\DLLs',
'c:\\programdata\\miniconda3\\lib',
'c:\\programdata\\miniconda3',
'',
'c:\\programdata\\miniconda3\\lib\\site-packages',
'c:\\programdata\\miniconda3\\lib\\site-packages\\win32',
'c:\\programdata\\miniconda3\\lib\\site-packages\\win32\\lib',
'c:\\programdata\\miniconda3\\lib\\site-packages\\Pythonwin',
'c:\\programdata\\miniconda3\\lib\\site-packages\\IPython\\extensions',
'C:\\Users\\Igarashi\\.ipython']
若自定义模块无法找到(所在目录不在系统环境变量),可以修改 path
告诉编译器目录位置
sys.path.append(abs_path)
dir()
用于查找某个模块下定义的所有名称(变量、模块、函数等),无参时列出当前定义的名称
import sys, builtins
dir(sys)
## 输出结果
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__',
...
'stderr', 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info', 'warnoptions', 'winver']
dir(builtins) # 查看内置函数和变量名
模块的查找顺序: 内存中加载 > 内置模块 > sys.path
路径中包含的模块
注意
Pycharm 中,可能会出现正确执行假象,IDE 将路径自动添加到了父包,但其实该路径并不在 sys.path
中
__file__
相对路径找绝对路径
利用 import os
import sys
os.path.abspath(__file__) # 把当前文件的相对路径变为绝对路径
os.path.dirname(__file__) # 找到当前路径所在的文件夹
# 上 上 级 父路径 加入到环境变量
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
# 同上,经常出现在配置文件中
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
1.4 Python "编译"
为了快速加载模块,Python 把模块的编译版缓存在文件名为 module.version.pyc
的 __pycache__
目录中, 对编译文件格式进行编码
version
一般为 Python 的版本号- 如,CPython 的 3.3 发行版,
spam.py
的编译版本缓存为__pycache__/spam.cpython-33.pyc
- 使用这种命名惯例,可让不同 Python 发行版,及不同版本的已编译模块,共存
- 如,CPython 的 3.3 发行版,
Python 会对比编译版本与源码的 修改日期,查看它是否已过期,是否要重新编译,此过程完全自动化
编译模块与平台无关,可在不同架构系统之间共享相同的支持库
提示
Python 在两种情况下不检查缓存
- 从命令行直接载入模块,只重新编译,不存储编译结果
- 没有源模块,就不会检查缓存
- 为了支持无源文件(仅编译)发行版本, 编译模块必须在源目录下,并且绝不能有源模块
2. 包
解释型语言的 包 并不是编译成低级语言(像 Java 编译为 .class
字节码文件)而后打包的意思,而是用 .module
构造模块命名空间的方法,是利用包按目录的形式,更加方便模块化和管理模块间的依赖
2.1 __init__.py
导入包时,Python 会搜索 sys.path
里的目录,查找包的子目录,会只将含有 __init__.py 文件的目录当成包
最简情况的 __init__.py
是一个空文件,其也可以执行包的初始化代码,或设置 __all__
变量
此时从包中导入单个模块
import sound.effects.echo
但通常不用全名形式写,而是
from sound.effects import echo # 导入包
from sound.effects.echo import echofilter # 从包中导入函数或变量
当一个包被 import
时,首先加载它的 __init__.py
文件,故也可在 __init__.py
文件下进行初始化
如,在 effects 文件下的 __init__.py
引入同级目录 format 文件下 wavread.py
的所有
""" __init__.py """
from sound.formats.wavread import *
此时 from.sound.effects import *
同时会包含 formats 下的 wavread
模块
2.2 __all__ 变量
如果包的 __init__.py
代码定义了列表 __all__
,运行 from package import *
时,会从 __all__
列表中导入
如作者在
sound/effects/__init__.py
下指定__all__ = ["echo", "surround"]
意味执行
from sound.effects import *
时,会导入echo
和surround
这两个子模块
简单来说就是 不想引入文件中的所有模块 或是 模块中的所有变量 而进行 限制 导入
2.3 __path__ 变量
包支持一个更特殊的属性 __path__ ,在 __init__.py
文件的代码被执行前,该属性会被初始化为自身所在的目录的列表
默认情况下只有一个元素,就是当前包的路径
修改
__path__
,就会改到此包内的搜索路径不常用,但可用于扩展包中的模块集
3. 调用解释器
Python 解释器(Linux 环境)通常安装在 /usr/local/bin/python3.10
路径下,将 /usr/local/bin
加入系统变量,即可键入 python3
启动,Windows 同理
3.1 调用模块
$ python -m fibo
这种形式即可调用 module
运行指定的 package
(包)
# 若需运行某项目的 main.py 入口命令如下
$ python -m etutorservice -c etc/default.yml -r
-m
即执行etutorservice
项目下的main.py
文件,此外,还可以运行 zip 的压缩文件-c
配置文件的路径,用 argparse(参数解析器)来获取执行脚本需要的参数-r
默认True
,用于初始化的判断
注意
python main.py
和 python -m main.py
区别
- 前者是直接运行,后者是把模块当脚本来启动,此时
__name__
为脚本名称,而非__main__
- 影响
sys.path
中的环境变量,-m
方式默认缺少当下目录路径,直接启动会将当前路径加入环境变量中
4. 目录规范
假设项目名为 Foo
,最方便快捷目录结构如下足够
Foo/
|-- bin/
| |-- foo
|
|-- src/
| |-- tests/
| | |-- __init__.py
| | |-- test_main.py
| |
| |-- __init__.py
| |-- main.py
|
|-- docs/
| |-- conf.py
| |-- abc.rst
|
|-- setup.py
|-- requirements.txt
|-- README
bin/
存放项目的一些 可执行文件,起名script/
之类的也行src/
存放项目的所有 源代码- 源代码中的所有模块、包都应该放在此目录,不要置于顶层目录
- 子目录
tests/
用来存放 单元测试 代码 - 程序的入口通常命名为
main.py
docs/
存放一些文档setup.py
安装、部署、打包的脚本,项目必须,为了 快速部署 & run 起来requirements.txt
存放软件依赖的外部 Python 包列表,类似前端的package.json
即可通过如下命令将所有的 Python 包依赖安装好
$ pip install -r requirements.txt
README.md
项目说明文件,通常使用 MarkDown 形式- 软件定位,软件的基本功能
- 运行代码的方法: 安装环境、启动命令等
- 简要的使用说明
- 代码目录结构说明,更详细点可以说明软件的基本原理
- 常见问题说明
除外,如
LICENSE.txt
、ChangeLog.txt
等,主要用来项目开源,写开源目录怎么组织,自行查阅
4.1 关于配置文件
很多项目对配置文件的使用做法是
- 配置文件写在一个或多个 Python 文件中,比如此处的
conf.py
- 项目中哪个模块用到这个配置文件就直接通过
import conf
形式在代码引入
如上做法问题
单元测试变得困难(因为模块内部依赖了外部配置)
配置文件作为用户控制程序的接口,应当 可由用户自由指定 该文件的路径
程序组件 可复用性太差,为这种贯穿所有模块的代码硬编码方式,使得大部分模块都依赖
conf.py
这个文件
配置使用更好的方式:
- 模块的配置都可 灵活配置 ,不受外部配置文件的影响
- 程序的配置都 灵活控制,如 Nginx、MySQL 这种软件的配置文件,可用户自由指定
- 故不应代码中
import conf
来使用配置文件
故如上目录结构中,没有将 conf.py
放在源码目录下,而是放在 docs/
目录下,给出的一个配置样例,而不是写死程序中
可通过给 main.py
设置启动参数指定配置路径的方式来让程序读取配置内容
$ python -m program -c doc/settings.py -r
conf.py
可换个类似名称,如settings.py
- 当然,也可用其他格式来编写配置文件,如
settings.yml
、default.yml