模块#
模块是包含 Python 定义和语句的文件。其文件名是模块名加后缀名 .py 。在模块内部,通过全局变量 name 可以获取模块名(即字符串)。
import os
import sys
module_path = os.path.abspath(os.path.join('.')) # 将当前jupyter目录加入到系统路径下面
if module_path not in sys.path:
sys.path.append(module_path)
import fibo
dir(fibo)
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'fib',
'fib2']
fibo.__name__
'fibo'
!cat fibo.py
# -*- coding: utf-8 -*-
# 斐波那契数列模块
def fib(n): # 打印斐波那契数列直到 n
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def fib2(n): # 返回斐波那契数列直到 n
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
if __name__ == "__main__":
print(fib(100))
!python3 fibo.py
0 1 1 2 3 5 8 13 21 34 55 89
None
导入包#
模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在 import 语句 第一次 遇到模块名时执行(文件作为脚本运行时,也会执行这些语句)。
每个模块都有自己的私有命名空间,它会被用作模块中定义的所有函数的全局命名空间。 我们可以通过与引用模块函数一样的标记法 modname.itemname 来访问一个模块的全局变量。
模块可以导入其他模块。 根据惯例可以将所有 import 语句都放在模块(或者也可以说是脚本)的开头但这并非强制要求。 如果被放置于一个模块的最高层级,则被导入的模块名称会被添加到该模块的全局命名空间。
import fibo
fibo.fib(1000) # 使用包里面函数
from fibo import fib, fib2 # 显示指定导入包里面的名称
from fibo import * # 导入所有不以下划线(_)开头的名称,不推荐,因为可能会覆盖已经定义的名称
import fibo as fib # 使用模块别名
from fibo import fib as fibonacci # 使用模块内部名称的别名
fibonacci(500)
import importlib # 默认只导入一次模块。如果更改了模块内容,可以重新加载模块
importlib.reload(modulename) # 重新加载模块名
以脚本方式执行模块#
可以用以下方式运行 Python 模块:
python fibo.py <arguments>
这项操作将执行模块里的代码,和导入模块一样,但会把 name 赋值为 “main”。 也就是把下列代码添加到模块末尾:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
模块搜索路径#
当导入一个名为 spam 的模块时,会安装如下流程搜索:
解释器首先会搜索具有该名称的内置模块(所有内置模块见:sys.builtin_module_names )
如果未找到,它将在变量 sys.path 所给出的目录列表中搜索名为 spam.py 的文件,sys.path初始化值来自:
被命令行直接运行的脚本所在的目录(或未指定文件时的当前目录)。
PYTHONPATH (目录列表,与 shell 变量 PATH 的语法一样)。
依赖于安装的默认值(按照惯例包括一个 site-packages 目录,由 site 模块处理)。
注意:
在支持符号链接的文件系统中,“被命令行直接运行的脚本所在的目录”是符号链接最终指向的目录。换句话说,符号链接所在的目录并 没有 被添加至模块搜索路径。
初始化后,Python 程序可以更改 sys.path。这样可以添加自定义的目录到搜索路径中:
import sys sys.path.append('/ufs/guido/lib/python')
“已编译的” Python 文件#
为了快速加载模块,Python 把模块的编译版本缓存在 pycache 目录中,文件名为 module.version.pyc,version 对编译文件格式进行编码,一般是 Python 的版本号。例如,CPython 的 3.3 发行版中,spam.py 的编译版本缓存为 pycache/spam.cpython-33.pyc。这种命名惯例让不同 Python 版本编译的模块可以共存。
Python 对比编译版与源码的修改日期,查看编译版是否已过期,是否要重新编译。此进程完全是自动的。此外,编译模块与平台无关,因此,可在不同架构的系统之间共享相同的库。
Python 在两种情况下不检查缓存。一,从命令行直接载入的模块,每次都会重新编译,且不储存编译结果;二,没有源模块,就不会检查缓存。为了让一个库能以隐藏源代码的形式分发(通过将所有源代码变为编译后的版本),编译后的模块必须放在源目录而非缓存目录中,并且源目录绝不能包含同名的未编译的源模块。
标准模块#
Python 的“标准模块”= 标准库(Standard Library)里所有随解释器一起发布、无需额外安装的模块。Python 3.11 版源码树中,标准库共 200+ 个包/模块,覆盖日常几乎所有需求:文件、网络、系统、并发、算法、测试、调试……
变量 sys.path 是字符串列表,用于确定解释器的模块搜索路径。该变量以环境变量 PYTHONPATH 提取的默认路径进行初始化,如未设置 PYTHONPATH,则使用内置的默认路径。可以用标准列表操作修改该变量:
import sys
sys.path.append('/ufs/guido/lib/python')
下面是标准库的“速查地图”:
>>> import sys
>>> sys.stdlib_module_names # Python 3.10+ 官方集合
>>> help('modules') # 交互式列出全部
>>> python -m pydoc -b # 一键开浏览器看标准库文档
操作系统 & 文件系统#
模块 |
一句话用途 |
---|---|
|
跨进程、文件/目录、环境变量、路径拼接 |
|
面向对象的文件系统路径(Python 3.4+) |
|
高级文件操作:复制、移动、递归删除 |
|
通配符匹配文件 |
|
安全临时文件/目录 |
文件格式 & 数据交换#
模块 |
一句话用途 |
---|---|
|
JSON ↔ Python |
|
读写 CSV |
|
内置 SQLite 数据库 |
|
INI 配置文件 |
|
压缩包读写 |
|
对象序列化/持久化 |
时间与日期#
模块 |
一句话用途 |
---|---|
|
日期、时间、时差、格式化 |
|
时间戳、睡眠、计时器 |
|
日历、闰年判断 |
进程 / 线程 / 并发#
模块 |
一句话用途 |
---|---|
|
生成新进程、管道通信 |
|
线程、锁、事件 |
|
绕过 GIL 的多进程、进程池 |
|
高层线程/进程池( |
|
原生异步协程框架 |
|
线程/进程安全队列 |
网络 & 互联网#
模块 |
一句话用途 |
---|---|
|
打开 URL、HTTP 请求 |
|
URL 拆分/组装/转义 |
|
一键静态文件 HTTP 服务器 |
|
FTP、发邮件、收邮件 |
|
底层 TCP/UDP 编程 |
|
TLS/SSL 封装 |
|
数据类型 & 算法#
模块 |
一句话用途 |
---|---|
|
高性能扩展容器: |
|
堆/优先队列 |
|
已排序列表二分插入/查找 |
|
紧凑数值数组 |
|
枚举类型 |
|
自动生成特殊方法的“数据类”(3.7+) |
|
类型注解支持 |
数学 & 随机#
模块 |
一句话用途 |
---|---|
|
浮点数学函数 |
|
复数数学函数 |
|
任意精度十进制(财务计算) |
|
有理数 |
|
伪随机数、随机抽样 |
|
均值、中位数、方差等统计函数 |
调试 / 测试 / 性能#
模块 |
一句话用途 |
---|---|
|
标准日志系统(线程安全、分级、Handler) |
|
单元测试框架(xUnit 风格) |
|
文档字符串里写测试 |
|
命令行调试器 |
|
打印/格式化异常栈 |
|
性能剖析、计时 |
|
追踪内存分配 |
语言服务 & 运行时#
模块 |
一句话用途 |
---|---|
|
解释器交互:路径、参数、退出、标准流 |
|
发出/过滤警告 |
|
与垃圾回收器交互 |
|
弱引用 |
|
动态导入、重载模块 |
|
扫描/解析 Python 源码 |
更多#
itertools
无限迭代器、排列组合、笛卡尔积functools
lru_cache
、partial
、函数式工具operator
用函数形式代替运算符contextlib
用@contextmanager
一行写上下文管理器argparse
命令行参数解析pathlib
面向对象路径
dir() 函数#
内置函数 dir() 用于查找模块定义的名称。返回结果是经过排序的字符串列表:
import sys
", ".join(dir(sys))
'__breakpointhook__, __displayhook__, __doc__, __excepthook__, __interactivehook__, __loader__, __name__, __package__, __spec__, __stderr__, __stdin__, __stdout__, __unraisablehook__, _base_executable, _clear_type_cache, _current_frames, _debugmallocstats, _framework, _getframe, _git, _home, _xoptions, abiflags, addaudithook, api_version, argv, audit, base_exec_prefix, base_prefix, breakpointhook, builtin_module_names, byteorder, call_tracing, copyright, displayhook, dont_write_bytecode, exc_info, excepthook, exec_prefix, executable, exit, flags, float_info, float_repr_style, get_asyncgen_hooks, get_coroutine_origin_tracking_depth, getallocatedblocks, getdefaultencoding, getdlopenflags, getfilesystemencodeerrors, getfilesystemencoding, getprofile, getrecursionlimit, getrefcount, getsizeof, getswitchinterval, gettrace, hash_info, hexversion, implementation, int_info, intern, is_finalizing, maxsize, maxunicode, meta_path, modules, path, path_hooks, path_importer_cache, platform, platlibdir, prefix, ps1, ps2, ps3, pycache_prefix, set_asyncgen_hooks, set_coroutine_origin_tracking_depth, setdlopenflags, setprofile, setrecursionlimit, setswitchinterval, settrace, stderr, stdin, stdout, thread_info, unraisablehook, version, version_info, warnoptions'
没有参数时,dir() 列出当前已定义的名称:
", ".join(dir())
'In, Out, _, _3, _4, _7, __, ___, __builtin__, __builtins__, __doc__, __loader__, __name__, __package__, __spec__, _dh, _exit_code, _i, _i1, _i2, _i3, _i4, _i5, _i6, _i7, _i8, _ih, _ii, _iii, _oh, exit, fibo, get_ipython, module_path, open, os, quit, sys'
dir() 不会列出内置函数和变量的名称。这些内容的定义在标准模块 builtins 中:
import builtins
", ".join(dir(builtins))
'ArithmeticError, AssertionError, AttributeError, BaseException, BlockingIOError, BrokenPipeError, BufferError, BytesWarning, ChildProcessError, ConnectionAbortedError, ConnectionError, ConnectionRefusedError, ConnectionResetError, DeprecationWarning, EOFError, Ellipsis, EnvironmentError, Exception, False, FileExistsError, FileNotFoundError, FloatingPointError, FutureWarning, GeneratorExit, IOError, ImportError, ImportWarning, IndentationError, IndexError, InterruptedError, IsADirectoryError, KeyError, KeyboardInterrupt, LookupError, MemoryError, ModuleNotFoundError, NameError, None, NotADirectoryError, NotImplemented, NotImplementedError, OSError, OverflowError, PendingDeprecationWarning, PermissionError, ProcessLookupError, RecursionError, ReferenceError, ResourceWarning, RuntimeError, RuntimeWarning, StopAsyncIteration, StopIteration, SyntaxError, SyntaxWarning, SystemError, SystemExit, TabError, TimeoutError, True, TypeError, UnboundLocalError, UnicodeDecodeError, UnicodeEncodeError, UnicodeError, UnicodeTranslateError, UnicodeWarning, UserWarning, ValueError, Warning, ZeroDivisionError, __IPYTHON__, __build_class__, __debug__, __doc__, __import__, __loader__, __name__, __package__, __spec__, abs, all, any, ascii, bin, bool, breakpoint, bytearray, bytes, callable, chr, classmethod, compile, complex, copyright, credits, delattr, dict, dir, display, divmod, enumerate, eval, exec, execfile, filter, float, format, frozenset, get_ipython, getattr, globals, hasattr, hash, help, hex, id, input, int, isinstance, issubclass, iter, len, license, list, locals, map, max, memoryview, min, next, object, oct, open, ord, pow, print, property, range, repr, reversed, round, runfile, set, setattr, slice, sorted, staticmethod, str, sum, super, tuple, type, vars, zip'
内置模块名称保存到sys.builtin_module_names
import sys
", ".join(sys.builtin_module_names)
'_abc, _ast, _codecs, _collections, _functools, _imp, _io, _locale, _operator, _peg_parser, _signal, _sre, _stat, _string, _symtable, _thread, _tracemalloc, _warnings, _weakref, atexit, builtins, errno, faulthandler, gc, itertools, marshal, posix, pwd, sys, time, xxsubtype'
包#
模块(module)是单个文件;包(package)是含 init.py 的目录,用来组织多个模块。
模块与包区别#
模块(module)#
本质:一个
.py
文件被
import foo
时,Python 把文件foo.py
编译成模块对象,名字就是文件名。例子:
utils.py → import utils
包(package)#
本质:一个带
__init__.py
的目录(Python 3.3+ 允许隐式命名空间包,但常规仍建议保留该文件)。目录名即包名,可嵌套子包;
__init__.py
在包首次被导入时执行,用于初始化、暴露接口。例子:
graphics/ __init__.py formats/ __init__.py png.py jpg.py filters/ __init__.py blur.py
使用:
from graphics.formats import png
包是通过使用“带点号模块名”来构造 Python 模块命名空间的一种方式。 例如,模块名 A.B 表示名为 A 的包中名为 B 的子模块。
示例包结构如下:
sound/ 最高层级的包
__init__.py 初始化 sound 包
formats/ 用于文件格式转换的子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ 用于音效的子包
__init__.py
echo.py
surround.py
reverse.py
...
filters/ 用于过滤器的子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
需要有 init.py 文件才能让 Python 将包含该文件的目录当作包来处理(除非使用 namespace package,这是一个相对高级的特性)。 这可以防止重名的目录如 string 在无意中屏蔽后继出现在模块搜索路径中的有效模块。 在最简单的情况下,init.py 可以只是一个空文件,但它也可以执行包的初始化代码或设置 all 变量。
导入模块#
导入单个模块:
import sound.effects.echo # 导入单个模块
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4) # 导入单个模块时候,必须通过其全名来引用
另外一种导入单个模块方法:
from sound.effects import echo
echo.echofilter(input, output, delay=0.7, atten=4) # 引用函数时候不需要再不必加包前缀
直接导入所需的函数或变量:
from sound.effects.echo import echofilter
echofilter(input, output, delay=0.7, atten=4)
注意:
使用 from package import item 时,item 可以是包的子模块(或子包),也可以是包中定义的函数、类或变量等其他名称。import 语句首先测试包中是否定义了 item;如果未在包中定义,则假定 item 是模块,并尝试加载。如果找不到 item,则触发 ImportError 异常。
相反,使用 import item.subitem.subsubitem 句法时,除最后一项外,每个 item 都必须是包;最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量。
从包中导入 *#
使用 from sound.effects import * 时会发生什么?你可能希望它会查找并导入包的所有子模块,但事实并非如此。因为这将花费很长的时间,并且可能会产生你不想要的副作用,如果这种副作用被你设计为只有在导入某个特定的子模块时才应该发生。
唯一的解决办法是提供包的显式索引。import 语句使用如下惯例:如果包的 init.py 代码定义了列表 all,运行 from package import * 时,它就是被导入的模块名列表。发布包的新版本时,包的作者应更新此列表。如果包的作者认为没有必要在包中执行导入 * 操作,也可以不提供此列表。例如,sound/effects/init.py 文件可以包含以下代码:
__all__ = ["echo", "surround", "reverse"]
这意味着 from sound.effects import * 将导入 sound.effects 包的三个命名子模块。
相对导入#
当包由多个子包构成(如示例中的 sound 包)时,可以使用绝对导入来引用同级包的子模块。 例如,如果 sound.filters.vocoder 模块需要使用 sound.effects 包中的 echo 模块,它可以使用 from sound.effects import echo。
可以编写相对导入代码,即使用 from module import name 形式的 import 语句。 这些导入使用前导点号来表示相对导入所涉及的当前包和上级包。 例如对于 surround 模块,可以使用:
from . import echo
from .. import formats
from ..filters import equalizer
注意: 相对导入基于当前模块名。因为主模块名永远是 “main” ,所以如果计划将一个模块用作 Python 应用程序的主模块,那么该模块内的导入语句必须始终使用绝对导入。
为什么在主模块中使用绝对导入?
避免歧义:如果你使用相对导入,如from .subpackage import module2,当模块作为脚本直接运行时,相对导入可能会失败,因为相对导入是相对于当前模块的,而主模块__main__没有父模块。
明确性:绝对导入清楚地表明了模块的来源,使得代码更容易理解和维护。
可移植性:当你的代码被重构或者模块被移动到不同的包中时,绝对导入可以确保导入语句仍然有效。
多目录中的包#
包还支持一个特殊属性 __path__
,可用于扩展包中的模块集。。在包的 __init__.py
中的代码被执行前,该属性被初始化为一个只含一项的列表,该项是一个字符串,是 __init__.py
所在目录的名称。可以修改此变量;这样做会改变在此包中搜索模块和子包的方式。