TensorFlow 持久化

Session Checkpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class tf.train.Saver:
def __init__(self,
var_list=None/list/dict,
reshape=False,
sharded=False,
max_to_keep=5,
keep_checkpoint_every_n_hours=10000.0,
name=None,
restore_sequentially=False,
saver_def=None,
builder=None,
defer_build=False,
allow_empty=False,
write_version=tf.train.SaverDef.V2,
pad_step_number=False,
save_relative_paths=False,
filename=None
):
self.last_checkpoints
  • 用途:保存Session中变量(张量值),将变量名映射至张量值

  • 参数

    • var_list:待保存、恢复变量,缺省所有
      • 变量需在tf.train.Saver实例化前创建
    • reshape:允许恢复并重新设定张量形状
    • sharded:碎片化保存至多个设备
    • max_to_keep:最多保存checkpoint数目
    • keep_checkpoint_every_n_hours:checkpoint有效时间
    • restore_sequentially:各设备中顺序恢复变量,可以 减少内存消耗
  • 成员

    • last_checkpoints:最近保存checkpoints

保存Session

1
2
3
4
5
6
7
8
9
10
def Saver.save(self,
sess,
save_path,
global_step=None/str,
latest_filename=None("checkpoint")/str,
meta_graph_suffix="meta",
write_meta_graph=True,
write_state=True
) -> str(path):
pass
  • 用途:保存Session,要求变量已初始化

  • 参数

    • global_step:添加至save_path以区别不同步骤
    • latest_filename:checkpoint文件名
    • meta_graph_suffix:MetaGraphDef文件名后缀

恢复Session

1
2
def Saver.restore(sess, save_path(str)):
pass
  • 用途:从save_path指明的路径中恢复模型
  • 模型路径可以通过Saver.last_checkpoints属性、 tf.train.get_checkpoint_state()函数获得

tf.train.get_checkpoint_state

1
2
3
4
5
def tf.train.get_checkpoint_state(
checkpoint_dir(str),
latest_filename=None
):
pass
  • 用途:获取指定checkpoint目录下checkpoint状态
    • 需要图结构已经建好、Session开启
    • 恢复模型得到的变量无需初始化
1
2
3
ckpt = tf.train.get_checkpoint_state(checkpoint_dir)
saver.restore(ckpt.model_checkpoint_path)
saver.restore(ckpt.all_model_checkpoint_paths[-1])

Graph Saver

tf.train.write_graph

1
2
3
4
5
6
def tf.train.write_graph(
graph_or_graph_def: tf.Graph,
logdir: str,
name: str,
as_text=True
)
  • 用途:存储图至文件中

  • 参数

    • as_text:以ASCII方式写入文件

Summary Saver

tf.summary.FileWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class tf.summary.FileWriter:
def __init__(self,
?path=str,
graph=tf.Graph
)

# 添加summary记录
def add_summary(self,
summary: OP,
global_step
):
pass

# 关闭`log`记录
def close(self):
pass
  • 用途:创建FileWriter对象用于记录log

    • 存储图到文件夹中,文件名由TF自行生成
    • 可通过TensorBoard组件查看生成的event log文件
  • 说明

    • 一般在图定义完成后、Session执行前创建FileWriter 对象,Session结束后关闭

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 # 创建自定义summary
with tf.name_scope("summaries"):
tf.summary.scalar("loss", self.loss)
tf.summary.scalar("accuracy", self.accuracy)
tf.summary.histogram("histogram loss", self.loss)
summary_op = tf.summary.merge_all()

saver = tf.train.Saver()

with tf.Session() as sess:
sess.run(tf.global_variables_initializer())

# 从checkpoint中恢复Session
ckpt = tf.train.get_check_state(os.path.dirname("checkpoint_dir"))
if ckpt and ckpt.model_check_path:
saver.restore(sess, ckpt.mode_checkpoint_path)

# summary存储图
writer = tf.summary.FileWriter("./graphs", sess.graph)
for index in range(10000):
loas_batch, _, summary = session.run([loss, optimizer, summary_op])
writer.add_summary(summary, global_step=index)

if (index + 1) % 1000 = 0:
saver.save(sess, "checkpoint_dir", index)

# 关闭`FileWriter`,生成event log文件
write.close()

TensorFlow训练

Optimizer

1
2
3
4
5
6
7
8
9
10
11
class tf.train.GradientDescentOptimizer:

class tf.train.AdagradOptimizer:

class tf.train.MomentumOptimizer:

class tf.train.AdamOptimizer:

class tf.train.FtrlOptimizer:

class tf.train.RMSPropOptmizer:

TensorFlow执行

Session

Session:TF中OPs对象执行、Tensor对象计算的封装环境

  • Session管理图、OPs

    • 所有图必须Session中执行
    • 将图中OPs、其执行方法分发到CPU、GPU、TPU等设备上
    • 为当前变量值分配内存
  • Session只执行Data/Tensor Flow中OPs,忽略不相关节点

    • 即通往fetches的flow中的OPs构成子图才会被计算
  • 各Session中各值独立维护,不会相互影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Session:
def __init__(self,
target=str,
graph=None/tf.Graph,
config=None/tf.ConfigProto)

# 获取Session中载入图
self.graph

# 关闭会话,释放资源
def close(self):
pass

# 支持上下文语法
def __enter__(self):
pass
def __exit__(self):
pass

# 执行TensorFlow中节点,获取`fetches`中节点值
# 返回值若为Tensor
# python中:以`np.ndarray`返回
# C++/C中:以`tensorflow:Tensor`返回
# 可直接调用`.eval()`获得单个OP值
def run(self,
fetches=tf.OPs/[tf.OPs],
feed_dict=None/dict,
options=None,
run_metadata=None)

tf.InteractiveSession

tf.InteractiveSession:开启交互式会话

1
2
3
4
5
6
7
8
9
10
11
 # 开启交互式Session
sess = tf.InteractiveSession()
a = tf.constant(5.0)
b = tf.constant(6.0)
c = a * b
x = tf.Variable([1.0, 2.0])
# 无需显式在`sess.run`中执行
# 直接调用`OPs.eval/run()`方法得到结果
x.initializer.run()
print(c.eval())
sess.close()

Session执行

  • Fetch机制:sess.run()执行图时,传入需取回的结果, 取回操作输出内容

  • Feed机制:通过feed_dict参数,使用自定义tensor值替代图 中任意feeable OPs的输出

    • tf.placeholder()表示创建占位符,执行Graph时必须 使用tensor替代
    • feed_dict只是函数参数,只在调用它的方法内有效, 方法执行完毕则消失
  • 可以通过feed_dict feed所有feedable tensor,placeholder 只是指明必须给某些提供值

    1
    2
    3
    4
    a = tf.add(2, 5)
    b = tf.multiply(a, 3)
    with tf.Session() as sess:
    sess.run(b, feed_dict={a: 15}) # 45

config参数选项

  • log_device_placement:打印每个操作所用设备
  • allow_soft_placement:允许不在GPU上执行操作自动迁移到 CPU上执行
  • gpu_options
    • allow_growth:按需分配显存
    • per_process_gpu_memory_fraction:指定每个GPU进程 使用显存比例(无法对单个GPU分别设置)
  • 具体配置参见tf.ConfigProto

Graph

Graph:表示TF中计算任务,

  • operation/node:Graph中节点,包括:operatorvariableconstant

    • 获取一个、多个Tensor执行计算、产生、返回tensor
    • 不能无法对其值直接进行访问、比较、操作
    • 图中节点可以命名,TF会自动给未命名节点命名
  • tensor:Graph中边,n维数组

    • TF中所有对象都是Operators
    • tensor是OPs执行结果,在图中传递/流动
  • 图计算模型优势

    • 优化能力强、节省计算资源
      • 缓冲自动重用
      • 常量折叠,只计算取得目标值过程中必须计算的值
      • 方便并行化
      • 自动权衡计算、存储效率
    • 易于部署
      • 可被割成子图(即极大连通分量),便于自动区分
      • 子图可以分发到不同的设备上分布式执行,即模型并行
      • 许多通用ML模型是通过有向图教学、可视化的
  • 图计算模型劣势

    • 难以debug
      • 图定义之后执行才会报错
      • 无法通过pdb、打印状态debug
    • 语法繁复

构建图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Graph:
def __init__(self):
pass

# 将当前图作为默认图
# 支持上下文语法
def as_default(self):
pass

# 强制OPs依赖关系(图中未反映),即优先执行指定OPs
# 支持上下文语法
def as_default(self):
def control_dependencies(self,
?ops=[tf.OPs])
pass

# 以ProtoBuf格式展示Graph
def as_graph_def(self):
pass

# 判断图中节点是否可以被feed
def is_feedable(self,
?op=tf.OPs):
pass
  • 可以通过tf.Graph创建新图,但最好在是在一张图中使用多个 不相连的子图,而不是多张图

    • 充分性:Session执行图时忽略不必要的OPs
    • 必要性
      • 多张图需要多个会话,每张图执行时默认尝试使用所有 可能资源
      • 不能通过python/numpy在图间传递数据(分布式系统)
  • 初始化即包含默认图,OP构造器默认为其增加节点

    • 通过tf.get_default_graph()获取

图相关方法

  • 获取TF初始化的默认图

    1
    2
    def tf.get_default_graph():
    pass

命名空间

1
2
3
4
5
6
7
8
 # 均支持、利用上下文语法,将OPs定义于其下
def tf.name_scope(name(str)):
pass
def tf.variable_scope(
name(str),
reuse=tf.AUTO_REUSE
):
pass
  • tf.name_scope:将变量分组
    • 只是将变量打包,不影响变量的重用、可见性等
    • 方便管理、查看graph
  • tf.variable_scope
    • 对变量有控制能力
      • 可设置变量重用性
      • 变量可见性局限于该variable scope内,即不同 variable scope间可以有完全同名变量 (未被TF添加顺序后缀)
    • 会隐式创建name scope
    • 大部分情况是用于实现变量共享
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def fully_connected(x, output_dim, scope):
# 设置variable scope中变量自动重用
# 或者调用`scope.reuse_variables()`声明变量可重用
with tf.variable_scope(scope, reuse=tf.AUTO_REUSE) as scope:
# 在当前variable scope中获取、创建变量
w = tf.get_variable("weights", [x.shape[1]], output_dim,
initializer=tf.random_normal_initializer())
b = tf.get_variable("biases", [output_dim],
initializer=tf.constant_initizer(0.0))
return tf.matmul(x, w) + b

def two_hidden_layers(x):
h1 = fully_connected(x, 50, "h1")
h2 =-fully_connected(h1, 10, "h2")

with tf.variable_scope("two_layers") as scope:
logits1 = two_hidden_layers(x1)
logits2 = two_hidden_layers(x2)

Lazy Loading

Lazy Loading:推迟声明/初始化OP对象至载入图时 (不是指TF的延迟计算,是个人代码结构问题,虽然是TF延迟图计算 模型的结果)

  • 延迟加载容易导致向图中添加大量重复节点,影响图的载入、 传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    x = tf.Variable(10, name='x')
    y = tf.Variable(20, name='y')

    with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    writer = tf.summary.FileWriter('graphs/lazy_loading', sess.graph)
    for _ in range(10):
    # 延迟加载节点,每次执行都会添加`tf.add`OP
    sess.run(tf.add(x, y))
    print(tf.get_default_graph().as_graph_def())
    writer.close()
  • 解决方案

    • 总是把(图的搭建)OPs的定义、执行分开
    • python利用@property装饰器,使用单例模式函数 封装变量控制,保证仅首次调用函数时才创建OP

Eager Execution

1
2
3
4
import tensorflow as tf
import tensorflow.contrib.eager as tfe
# 启用TF eager execution
tfe.enable_eager_execution()
  • 优势

    • 支持python debug工具
    • 提供实时报错
    • 支持python数据结构
    • 支持pythonic的控制流

      1
      2
      3
      i = tf.constant(0)
      whlile i < 1000:
      i = tf.add(i, 1)
  • eager execution开启后

    • tensors行为类似np.ndarray
    • 大部分API和未开启同样工作,倾向于使用
      • tfe.Variable
      • tf.contrib.summary
      • tfe.Iterator
      • tfe.py_func
      • 面向对象的layers
      • 需要自行管理变量存储
  • eager execution和graph大部分兼容

    • checkpoint兼容
    • 代码可以同时用于python过程、构建图
    • 可使用@tfe.function将计算编译为图

示例

  • placeholder、sessions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 普通TF
    x = tf.placholder(tf.float32, shape=[1, 1])
    m = tf.matmul(x, x)
    with tf.Session() as sess:
    m_out = sess.run(m, feed_dict={x: [[2.]]})

    # Eager Execution
    x = [[2.]]
    m = tf.matmul(x, x)
  • Lazy loading

    1
    2
    3
    4
    5
    x = tf.random_uniform([2, 2])
    for i in range(x.shape[0]):
    for j in range(x.shape[1]):
    # 不会添加多个节点
    print(x[i, j])

Device

设备标识

  • 设备标识:设备使用字符串进行标识

    • /cpu:0:所有CPU都以此作为名称
    • /gpu:0:第一个GPU,如果有
    • /gpu:1:第二个GPU
    1
    2
    3
    4
    5
    6
    # 为计算指定硬件资源
    with tf.device("/gpu:2"):
    a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0], name="a")
    b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0], name="b")
    c = tf.multiply(a, b)
    # creates a graph
  • 环境变量:python中既可以修改os.environ,也可以直接设置 中设置环境变量

    • CUDA_VISIBLE_DEVICES:可被使用的GPU id

设备执行

设备执行:TF将图形定义转换成分布式执行的操作以充分利用计算 资源

  • TF默认自动检测硬件配置,尽可能使用找到首个GPU执行操作

  • TF会默认占用所有GPU及每个GPU的所有显存(可配置)

    • 但只有一个GPU参与计算,其他GPU默认不参与计算(显存 仍然被占用),需要明确将OPs指派给其执行
    • 指派GPU数量需通过设置环境变量实现
    • 控制显存占用需设置Session config参数
  • 注意:有些操作不能再GPU上完成,手动指派计算设备需要注意

tf.ConfigProto

  • Session参数配置

    1
    2
    3
    4
    5
     # `tf.ConfigProto`配置方法
    conf = tf.ConfigProto(log_device_placement=True)
    conf.gpu_options.allow_growth=True
    sess = tf.Session(config=conf)
    sess.close()

Lexical Analysis

  • python将读取的程序问题转换为Unicode码点
    • 源文件的文本编码可由编码声明指定
    • 默认UTF-8
  • 词法分析器将文件拆分为token
  • 解释器以词法分析器生成的token流作为输入

行结构

逻辑行

逻辑行:逻辑行的结束以NEWLINE token表示

  • 语句不能跨越逻辑行边集,除非其语法允许包含NEWLINE (如复合语句包含多个子语句)
  • 逻辑行可由一个、多个物理行按照明确、隐含行拼接规则 构成

python程序可以分为很多逻辑行

物理行

物理行:以行终止序列结束的字符序列

  • 源文件、字符串中可以使用任何标准平台上行终止序列
    • Unix:\n换行符LF
    • Win:\r\n回车加换行CR LF
    • Macintosh:\r回车CR
  • 输入结束会被作为最后物理行的隐含终止标志
  • 嵌入Python源码字符串应使用标准C传统换行符\n

显式行拼接

显式行拼接:两个、多个物理行使用\拼接为一个逻辑行

  • 物理行以不在字符串、注释内的反斜杠结尾时,将与下行 拼接构成一个单独逻辑行
    • 反斜杠、其后换行符将被删除
  • 以反斜杠结束的行不能带有注释
  • 反斜杠不能用于
    • 拼接注释
    • 拼接字符串外token
  • 不允许原文字符串以外反斜杠存在于物理行其他位置

隐式行拼接

隐式行拼接

  • 圆括号、方括号、花括号内表达式允许被分为多个物理行,无需 使用反斜杠

    • 拼接行可以带有注释
    • 后续行缩进不影响程序结构、允许为空白行
    • 拼接行之间不会有NEWLINE token
  • 三引号"""/'''字符串允许被分为多个物理行

    • 拼接行中不允许带有注释

空白行

空白行:只包含空格符、制表符、进纸符、注释的逻辑行会被忽略, 不生成NEWLINE token

  • 交互式输入语句时,对空白行处理可能因为读取-求值-打印循环 的具体实现而存在差异

    • 标准交互模式解释器中:完全空白逻辑行将会结束一条多行 复合语句

注释

注释:一不包含在字符串内的#开头,在物理行末尾结束

  • 注释标志逻辑行的结束,除非存在隐含行拼接规则
  • 注释在语法分析中被忽略,不属于token

编码声明

编码声明:位于python脚本第一、第二行,匹配正则表达式 coding[=:]\s*([-\w.]+)的注释将被作为编码声明处理

1
# -*- coding: <encoding-name> -*-
  • 表达式第一组指定了源码文件编码
    • 编码声明指定编码名称必须是python所认可的编码
    • 词法分析将使用此编码:语义字符串、注释、标识符
  • 编码声明须独占一行,若在第二行,则第一行也必须为注释
  • 没有编码声明,默认编码为UTF-8
    • 若文件首字节为UTF-8字节顺序标志b\xef\xbb\xbf, 文件编码也声明为UTF-8

缩进

缩进:逻辑行开头的空白(空格符、制表符)被用于计算该行 的缩进等级,决定语句段落组织结构

  • 首个非空白字符之前的空格总数确定该行的缩进层次
    • 缩进不能使用反斜杠进行拼接,首个反斜杠之前空格将确定 缩进层次
  • 制表符被替换为1-8个空白,使得缩进的空格总数为8倍数
    • 源文件若混合使用制表符、空格符缩进,并使得确定缩进 层次需要依赖于制表符对应空格数量设置,将引发 TabError
    • 由于非Unix平台上文本编辑器本身特性,源文件中混合使用 制表符、空格符是不明智的
  • 进纸符
    • 在行首时:在缩进层级计算中被忽略
    • 行首空格内其他位置:效果未定义,可能导致空格计数重置 为0
  • 不同平台可能会显式限制最大缩进层级

INDENT/DEDENT token

  • 读取文件第一行前,向堆栈中压入零值,不再弹出
  • 被压入栈的层级数值从底至顶持续增加
  • 每个逻辑行开头的行缩进层级将和栈顶进行比较
    • 相同:不做处理
    • 新行层级较高:压入栈中,生成INDENT token
    • 新行层级较低:应为栈中层级数值之一
      • 栈中高于该层级所有数值被弹出
      • 每弹出一级数值生成一个DEDENT token
  • 文件末尾,栈中剩余每个大于0数值生成一个DEDENT token

Tokens

型符

  • 空白字符不属于token
    • 除逻辑行开头、字符串内,空格符、制表符、进纸符等 空白符均可分隔token
    • 否则彼此相连的token会被解析为一个不同的token
  • 若存在二义性,将从左至右尽可能长读取合法字符串组成token

Indentifiers

标识符/名称:python标识符语法基于Unicode标准附件UAX-31,有 修改

  • ASCII字符集内可用于标识符与py2一致

    • 大、小写字母
    • 下划线_
    • 数字0-9
  • py3中引入ASCII字符集以外的额外字符

    • 其分类使用包含于unicodedata模块中Unicode字符数据库 版本
  • 标识符没有长度限制、大小写敏感

1
2
3
4
5
identifier   ::=  xid_start xid_continue*
id_start ::= <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
id_continue ::= <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
xid_start ::= <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">
xid_continue ::= <all characters in id_continue whose NFKC normalization is in "id_continue*">
  • Lu:大写字母
  • Ll:小写字母
  • Lt:词首大写字母
  • Lm:修饰字母
  • Lo:其他字母
  • Nl:字母数字
  • Mn:非空白标识
  • Mc:含空白标识
  • Nd:十进制数字
  • Pc:连接标点
  • Other_ID_Start:由PropList.txt定义的显式字符列表, 用来支持向后兼容
  • Other_ID_Continue:同上

Keywords

关键字:以下标识符作为语言的保留字、关键字,不能用作普通 标识符

1
2
3
4
5
6
7
False      await      else       import     pass
None break except in raise
True class finally is return
and continue for lambda try
as def from nonlocal while
assert del global not with
async elif if or yield

保留标识符类

以下划线字符开头、结尾的标识符类:具有特殊函数

  • _*:不会被from module import *导入

    • 特殊标识符_:交互式解释器中用于存放最近一次求值 结果,不处于交互模式时无特殊含义、无预定义
  • __*__:系统定义名称

    • 由解释器极其实现(包括标准库)定义
    • 任何不遵循文档指定方式使用__*__行为可能导致无警告 出错
  • __*:类私有名称

    • 在类定义中使用
    • 会以混合形式重写避免基类、派生类私有属性之间出现 名称冲突

字面值

字面值:表示一些内置类型常量

字符串、字节串字面值

1
2
3
4
5
6
7
8
9
10
stringliteral   ::=  [stringprefix](shortstring | longstring)
stringprefix ::= "r" | "u" | "R" | "U" | "f" | "F"
| "fr" | "Fr" | "fR" | "FR" | "rf" | "rF" | "Rf" | "RF"
shortstring ::= "'" shortstringitem* "'" | '"' shortstringitem* '"'
longstring ::= "'''" longstringitem* "'''" | '"""' longstringitem* '"""'
shortstringitem ::= shortstringchar | stringescapeseq
longstringitem ::= longstringchar | stringescapeseq
shortstringchar ::= <any source character except "\" or newline or the quote>
longstringchar ::= <any source character except "\">
stringescapeseq ::= "\" <any source character>
1
2
3
4
5
6
7
8
9
bytesliteral   ::=  bytesprefix(shortbytes | longbytes)
bytesprefix ::= "b" | "B" | "br" | "Br" | "bR" | "BR" | "rb" | "rB" | "Rb" | "RB"
shortbytes ::= "'" shortbytesitem* "'" | '"' shortbytesitem* '"'
longbytes ::= "'''" longbytesitem* "'''" | '"""' longbytesitem* '"""'
shortbytesitem ::= shortbyteschar | bytesescapeseq
longbytesitem ::= longbyteschar | bytesescapeseq
shortbyteschar ::= <any ASCII character except "\" or newline or the quote>
longbyteschar ::= <any ASCII character except "\">
bytesescapeseq ::= "\" <any ASCII character>
  • stringprefixbytesprefix与字面值剩余部分之间不允许 由空白符
  • 源字符集由编码声明定义
  • 字节串字面值只允许ASCII字符(但允许存储不大于256)
  • 两种字面值都可以使用成对(连续三个)单引号、双引号标示 首尾

    • 单引号'':允许包含双引号""
    • 双引号"":允许包含单引号''
    • 三重引号'''"""
      • 原样保留:未经转义的换行、(非三联)引号、空白符
  • 反斜杠\用于对特殊含义字符进行转义

字符串前缀
  • b/B前缀:字节串字面值

    • 创建bytes类型而非str类型实例
    • 只能包含ASCII字符
    • 字节对应数值大于128必须以转义形式表示
  • r/R:原始字符串/字节串

    • 其中反斜杠\被当作其本身字面字符处理
    • 转换序列不在有效
    • 原始字面值不能以单个\结束,会转义之后引号字符
  • f/F:格式化字符串字面值

转义规则
  • 字符串、字节串字面值中转义序列基本类似标准C转义规则

    • \xhh:必须接受2个16进制数码
  • 以下转义序列仅在字符串字面值中可用

    • \N{name}:Unicode数据库中名称为name的字符
    • \uxxxx:必须接受4个16进制数码
    • \Uxxxxxxxx:必须接受8个16进制数码
  • 无法识别的转义序列

    • py3.6之前将原样保留在字符串中
    • py3.6开始,将引发DeprecationWarning,未来可能会 引发SyntaxError
字符串字面值拼接
  • 多个相邻字符串、字符串字面值(空白符分隔),含义等同于 全部拼接为一体

    • 所用引号可以彼此不同(三引号风格也可用)
    • 每部分字符串可以分别加注释
    • 可以包括格式化字符串字面值
  • 此特性是在句法层面定义,在编译时实现

    • 在运行时拼接字符串表达式必须使用+运算符
格式化字符串字面值

格式化字符串字面值:带有f/F前缀的字符串字面值

  • 包含可替换字段,即以{}标示的格式表达式

    • 字符串{}以外部分按字面值处理
    • 双重花括号{ { } }被替换为相应单个花括号
  • 格式表达式被当作正常的包含在圆括号中python表达式处理 ,在运行时从左至右被求值

    • 不允许空表达式
    • lambda空表达式必须显式加上圆括号
    • 可以包含换行:如三引号字符串
    • 不能包含注释
    • 不能\反斜杠,考虑创建临时变量
  • 格式化字符串字面值可以拼接,但是一个替换字段不能拆分到 多个字面值中

  • 格式化字符串不能用作文档字符串,即使其中没有包含表达式

1
2
3
4
5
6
7
8
f_string          ::=  (literal_char | "{{" | "}}" | replacement_field)*
replacement_field ::= "{" f_expression ["!" conversion] [":" format_spec] "}"
f_expression ::= (conditional_expression | "*" or_expr)
("," conditional_expression | "," "*" or_expr)* [","]
| yield_expression
conversion ::= "s" | "r" | "a"
format_spec ::= (literal_char | NULL | replacement_field)*
literal_char ::= <any code point except "{", "}" or NULL>
  • !:标识转换字段

    • !s:对结果调用str()
    • !r:调用repr()
    • !a:调用ascii()
    1
    2
    3
    4
    name = "Fred"
    print(f"he said his name is {name!r}")
    print("he said his name is {repr(name)}")
    # 二者等价
  • ::标识格式说明符,结果使用format()协议格式化

    • 格式说明符被传入表达式或转换结果的.__format__() 方法
    • 省略格式说明符则传入空字符串
    1
    2
    3
    4
    5
    6
    7
    8
    9
    width, precision = 4, 10
    value = decimal.Deciaml("12.345")
    print(f"result: {value: {width}.{precision}}")

    today = datetime(yeat=2017, month=1, day=27)
    print(f"{today: %B %d, %Y}")

    number = 1024
    print(f"{number: #0x}")
  • 顶层格式说明符可以包含嵌套替换字段

    • 嵌套字段可以包含有自身的转换字段、格式说明符,但不能 包含更深层嵌套替换字段

数字字面值

  • 数字字面值不包括正负号
    • 负数实际上是由单目运算符-和字面值组合而成
  • 没有专门复数字面值
    • 复数以一对浮点数表示
    • 取值范围同浮点数
  • 数字字面值可以使用下划线_将数码分组提高可读性
    • 确定数字大小时,字面值之间的下滑线被忽略
    • 下划线可以放在数码之间、基数说明符0x等之后
    • 浮点数中不能直接放在.
整形数字字面值
  • 整形数字字面值没有长度限制,只受限于内存
1
2
3
4
5
6
7
8
9
10
integer      ::=  decinteger | bininteger | octinteger | hexinteger
decinteger ::= nonzerodigit (["_"] digit)* | "0"+ (["_"] "0")*
bininteger ::= "0" ("b" | "B") (["_"] bindigit)+
octinteger ::= "0" ("o" | "O") (["_"] octdigit)+
hexinteger ::= "0" ("x" | "X") (["_"] hexdigit)+
nonzerodigit ::= "1"..."9"
digit ::= "0"..."9"
bindigit ::= "0" | "1"
octdigit ::= "0"..."7"
hexdigit ::= digit | "a"..."f" | "A"..."F"
浮点数字字面值
  • 整形数部分、指数部分解析时总以10为计数
  • 浮点数字面值允许范围依赖于具体实现
1
2
3
4
5
6
floatnumber   ::=  pointfloat | exponentfloat
pointfloat ::= [digitpart] fraction | digitpart "."
exponentfloat ::= (digitpart | pointfloat) exponent
digitpart ::= digit (["_"] digit)*
fraction ::= "." digitpart
exponent ::= ("e" | "E") ["+" | "-"] digitpart
虚数字面值
  • 序数字面值将生成实部为0.0的复数
1
imagnumber ::=  (floatnumber | digitpart) ("j" | "J")

运算符

1
2
3
+       -       *       **      /       //      %      @
<< >> & | ^ ~
< > <= >= == !=

分隔符

1
2
3
4
(       )       [       ]       {       }
, : . ; @ = ->
+= -= *= /= //= %= @=
&= |= ^= >>= <<= **=
  • 以上列表中后半部分为增强赋值操作符
    • 在词法中作为分隔符,同时起运算作用
1
'       "       #       \
  • 以上可打印ASCII字符
    • 作为其他token组成部分时有特殊意义
    • 或对词法分析器有特殊意义
1
$       ?
  • 以上可打印ASCII字符不再Python词法中使用
    • 出现在字符串字面值、注释之外将无条件引发错误

Python执行模型

综述

Code Blocks

代码块:作为一个单元执行的一段python文本,代码块构成python 程序

  • 模块、函数体、类定义
  • 交互式输入的每条命令
  • 作为标准输入、命令行参数发送给解释器的脚本文件
  • 脚本命令
  • 传递给eval()exec()的字符串参数
  • 代码块在执行帧中执行,执行帧包含某些用于调试的管理信息 并决定代码块执行完成后操作

NamingBinding

Binding of Names

  • 名称:用于指代对象,通过名称绑定操作引入
  • 名称绑定方法

    • 传递参数
    • import语句
      • from ... import *会绑定被导入模块中定义的所有 公有名称(仅在模块层级上被使用)
    • 类、函数定义
    • 以标识符为目标的赋值
    • for循环开头、withexcept子句的as之后

    • del语句的目标也被视为绑定,虽然实际语义为解除名称 绑定

  • 变量类型

    • 局部变量:绑定在代码块中的名称,且未声明为nonlocal 、或global
    • 全局变量:绑定在模块层级的名称
      • 模块代码块中变量既为全局变量、也是局部变量
    • 自由变量:在代码块中使用但未在其中定义的变量
  • 自由变量不同于闭包变量,函数闭包变量__closure__要求 自由变量来自于父函数作用域

Scope

作用域:定义了代码块中名称的可见性

  • 名称的作用域包含

    • 定义该变量代码块
    • 定义变量代码块所包含的代码块 (除非被包含代码块中引入对该名称不同绑定)
  • 名称作用域虽然包括定义变量代码块所包含的代码块

    • 可以直接访问变量
    • 但修改变量必须使用globallocal等声明
  • 在代码块内任何位置进行名称绑定,则代码块内所有对该名称 的使用被认为是对代码块的引用

    • python没有声明语法,则代码块中局部变量可通过,在整个 代码块文本中扫描名称绑定来确定
  • 模块作用域在模块第一次被导入时创建

Resolution of Names

  • 若名称在代码块中被使用,会由包含它的最近作用域来解析

  • 若名称完全无法找到将raise NameError

  • 若当前作用域为函数作用域,且名称指向局部变量,若名称被 绑定值前被被使用将raise UnboundLocalErrorUnboundLocalErrorNameError子类)

  • (代码块)环境:对代码块可见的所作用域集合

global

  • global指定名称的使用是对最高层级命名空间中该名称 绑定的引用

    • 全局命名空间:包含代码块的模块命名空间
    • 内置命名空间:builtins模块的命名空间
  • global语句与同一代码块中名称绑定具有相同作用域

    • 若自由变量最近包含作用域中有global语句,其也会被 当作全局变量
  • global语句须在名称使用之前声明

nonlocal

  • nonlocal使得相应名称指向最近包含函数作用域的中绑定的 变量

  • 指定名称不存在于任何包含函数作用域则raise SyntaxError

内置命名空间

  • 与代码块执行相关联的内置命名空间实际上是通过其在全局命名 空间中搜索名称__builtins__找到,一般是字典、模块 (此时使用模块的命名空间字典)

  • 默认情况下

    • __main__模块中:__builtins__为内置模块builtins
    • __main__模块中:__buitlins__builtins模块 自身的字典别名

动态特性

  • 自由变量的名称解析

    1
    2
    3
    4
    5
    6
    i = 0
    def f():
    print(i)
    i = 42
    f()
    # 打印`42`
    • 发生在运行时,而不是编译时
    • 在全局命名空间中,而不是最近包含命名空间中

eval()exec()

  • eval()exec()没有对完整环境的访问权限来解析名称

    • 有可选参数用于重载全局、局部命名空间
    • 若只指定一个命名空间,则会同时作用于两者

  • 类中名称解析遵守大部分情况下同普通规则,但 未绑定局部变将在全局命名空间中搜索

  • 类定义的命名空间__dict__会成为类的属性字典

  • 类代码块中定义的名称的作用域会被限制在类代码块中,不会 扩展到方法代码块中,包括推导式、生成器表达式

    1
    2
    3
    class A:
    a = 42
    b = list(a + i for i in range(10))

Exception

异常:中断代码块的正常控制流程以便处理错误、其他异常条件的 方式

  • 在错误被检测到的位置引发

    • 解释器检测到运行时错误时错误
    • raise语句显式引发
  • 可被当前包围代码块、任何直接或间接主调代码块捕获、处理

    • try...except指定错误处理,finally子句清理代码
    • 异常通过实例标识、根据器类型选择执行except子句
  • 错误处理采用“终止”模型

    • 异常处理器可以找出发生的问题,在外层继续执行
    • 但不能修复错误的根源,并重试失败操作
    • 异常完全未被处理时,解释器会终止程序执行、或返回交互 模式循环,并打印栈回溯信息,除非为SystemExit异常
  • 异常实例可以携带关于异常状态的额外信息

    • 异常信息不是python API一部分,内容可能在不同python 版本间不经警告的改变

Simple Statements

简单语句:由单个逻辑行构成

  • 多条简单语句可以在同一物理行内、并以分号分隔
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
simple_stmt ::=  expression_stmt
| assert_stmt
| assignment_stmt
| augmented_assignment_stmt
| annotated_assignment_stmt
| pass_stmt
| del_stmt
| return_stmt
| yield_stmt
| raise_stmt
| break_stmt
| continue_stmt
| import_stmt
| future_stmt
| global_stmt
| nonlocal_stmt

Expression Statements

表达式语句:用于计算、写入值(交互模式下),或调用过程(不 返回有意义结果的函数)

1
expresssion_stmt ::= starred_expression
  • 用途:表达式语句对指定表达式[列表]进行求值
  • 交互模式下
    • 若值不为None:通过内置repr()函数转换为字符串, 单独一行写入标准输出
    • 值为None:不产生任何输出

Assignment Statements

赋值语句:将名称[重]绑定到特定值、修改属性或可变对象成员项

1
2
3
4
5
6
7
8
9
assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)
target_list ::= target ("," target)* [","]
target ::= identifier
| "(" [target_list] ")"
| "[" [target_list] "]"
| attributeref
| subscription
| slicing
| "*" target
  • 用途:对指定表达式列表求值,将单一结果对象从左至右逐个 赋值给目标列表

  • 赋值根据目标列表的格式递归定义,目标为可变对象组成部分时 (属性引用、抽取、切片),可变对象赋值有效性会被检查, 赋值操作不可接受可能引发异常

  • 赋值顺序:将赋值看作是左、右端项重叠

    • 根据定义赋值语句内多个赋值是同时重叠,如:a,b=b,a 交换两变量值
    • 但赋值语句左端项包含集合类型时,重叠从左到右依次执行

      1
      2
      3
      4
      x = [0,1]
      i = 0
      i, x[i] = 1, 2
      # `x`现在为`[0, 2]`
      • LOAD_XXX指令将右端项从左到右依次压栈
      • ROT_N指令交换栈顶元素
      • STORE_XXX指令将栈顶元素弹出从左到右依次给 右端项赋值
      • 以上语句中,计算表达式x[i]i已经被赋新值
      • dis.dis()查看代码块指令

赋值目标为列表

赋值目标(左端项)为列表(可选包含在圆括号、方括号内)

  • 若目标列表为不带逗号、可以包含在圆括号内的单一目标,将 右端项赋值给该目标

  • 否则:右端项须为与目标列表相同项数的可迭代对象,其中 元素将从左至右顺序被赋值给对应目标

  • 若目标列表包含带*元素:则类似实参解包,其须为可迭代 对象,且右端项至少包含目标列表项数-1

    • 加星目标前元素被右端项前段元素一一赋值
    • 加星目标后元素被右端项后段元素一一赋值
    • 加星目标被赋予剩余目标元素构成的列表

赋值目标为单个目标

目标为标识符(名称)

  • 名称未出现在当前代码块globalnonlocal语句中:名称 被绑定到/赋值为当前局部命名空间对象

  • 否则:名称被分别绑定到/赋值为全局命名空间、或nonlocal 确定的外层命名空间中对象

  • 若名称已经被绑定则被重新绑定,可能导致之前被绑定名称 对象引用计数变为0,对象进入释放过程并调用其析构器

目标为属性引用

  • 引用中原型表达式被求值:应产生具有可赋值属性的对象, 否则raise TypeError

  • 该对象指定属性将被赋值,若无法赋值将 raise AttributeError

目标为抽取项

  • 引用中原型表达式被求值:应产生可变序列对象(列表)、 映射对象(字典)

  • 引用中抽取表达式被求值

    • 若原型表达式求值为可变序列对象

      • 抽取表达式产生整数,包含负数则取模,结果只须为 小于序列长度非负整数
      • 整数指定的索引号的项将被赋值
      • 若索引超出范围将raise IndexError
    • 若原型表达式求值为映射对象

      • 抽取表达式须产生与该映射键类型兼容的类型
      • 映射可以创建、更新抽取表达式指定键值对
  • 对用户自定义对象,将调用__setitem__方法并附带适当参数

目标为切片

  • 引用中原型表达式被求值:应当产生可变序列对象

    • 右端项应当是相同类型的序列对象
  • 上界、下界表达式若存在将被求值

    • 其应为整数,若为负数将对原型表达式序列长度求模,最终 边界被裁剪至0、序列长度开区间中
    • 默认值分别为零、序列长度
  • 切片被赋值为右端项

    • 若切片长度和右端项长度不同,将在目标序列允许情况下 改变目标序列长度

Augmented Assignment Statements

增强赋值语句:在单个语句中将二元运算和赋值语句合为一体

1
2
3
4
augmented_assignment_stmt ::=  augtarget augop (expression_list | yield_expression)
augtarget ::= identifier | attributeref | subscription | slicing
augop ::= "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**="
| ">>=" | "<<=" | "&=" | "^=" | "|="
  • 增强赋值语句不能类似普通赋值语句为可迭代对象拆包
  • 赋值增强语句对目标和表达式列表求值

    • 依据两操作数类型指定执行二元运算
    • 将结果赋给原始目标
  • 增强赋值语句可以被改写成类似、但非完全等价的普通赋值语句

    • 增强赋值语句中目标仅会被求值一次
    • 在可能情况下,运算是原地执行的,即直接修改原对象而 不是创建新对象并赋值给原对象
    • 所以增强赋值先对左端项求值
  • 其他增强赋值语句和普通赋值语句不同点

    • 单条语句中对元组、多目标赋值赋值操作处理不同

Annotated Assignment Statements

带标注的赋值语句:在单个语句中将变量、或属性标注同可选赋值 赋值语句合并

1
annotated_assignment_stmt ::=  augtarget ":" expression ["=" expression]
  • 与普通赋值语句区别仅在于:仅有单个目标、且仅有单个右端项 才被允许

  • 在类、模块作用域中

    • 若赋值目标为简单名称,标注会被存入类、模块的 __annotations__属性中
    • 若赋值目标为表达式,标注被求值但不会被保存
  • 在函数作用域内,标注不会被求值、保存

  • 若存在右端项,带标注赋值在对标注值求值前执行实际赋值; 否则仅对赋值目标求值,不执行__setitem____setattr__

    • 参见cs_python/py3ref/#todo

关键字语句

assert

assert语句:在程序中插入调试性断言的简便方式

1
assert_stmt ::= "assert" expression ["," expression]
  • assert语句等价于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if __debug__:
    if not expression:
    raise AssertionError
    # 等价于`assert expression`

    if __debug__:
    if not expression1:
    raise AssertionError(expression2)
    # 等价于`assert expression1, expression2`
    • 无需再错误信息中包含失败表达式源码,其会被作为栈追踪 一部分被显示
  • 假定__debug__AssertionError指向具有特定名称的内置 变量,当前实现中

    • __debug__赋值是非法的,其值在解释器启动时确定
    • 默认内置变量__debug__=True
    • 请求优化时__debug__置为False
      • -0命令行参数开启
      • 若编译时请求优化,代码生成器不会为assert语句生成 代码

pass

pass语句:空操作,被执行时无事情发生

1
pass_stmt ::= "pass"
  • 适合语法上需要语句、但不需要执行代码时临时占位

del

del语句:从局部、全局命名空间中移除名称的绑定,若名称未绑定 将raise NameError

1
del_stmt ::= "del" target_list
  • 删除是递归定义的

    • 类似赋值的定义方式
    • 从左至右递归的删除目标列表中每个目标
  • 属性、抽取、切片的删除会被传递给相应原型对象

    • 删除切片基本等价于赋值为目标类型的空切片???

return

return语句:离开当前函数调用,返回列表表达式值、None

  • 在生成器函数中,return语句表示生成器已完成

    • raise StopIteration
    • 返回值作为参数构建StopIteration,并作为 StopIteration.value属性
  • 异步生成器函数中,return语句表示异步生成器已完成

    • raise StopAsyncIteration
    • 非空return返回值在将导致语法错误
1
return_stmt ::= "return" [expression_list]
  • return语法上只会出现于函数定义代码块中,不会出现于 类定义所嵌套代码中

  • 若提供表达式列表,其将被求值;否则缺省为None

  • return将控制流传出带有finally子句的try语句时, finally子句会先被执行然后真正离开函数

yield

yield语句:语义上等于yield表达式

1
yield_stmt ::= yield_expression
  • 可用于省略在使用等效yield表达式必须的圆括号

    1
    2
    3
    4
    5
    6
    yield <expr>
    yield from <expr>
    # 以上yield语句、以下yield表达式等价

    (yield <expr>)
    (yield from <expr>)
  • yeild表达式、语句仅在定义[异步]生成器函数时使用,且仅 用于函数体内部,且函数体包含yield就使得该定义创建 生成器函数而非普通函数

  • yield表达式参见cs_python/py3ref/#todo

raise

raise语句:引发异常

1
raise_stmt ::= "raise" [expression ["from" expression]]
  • 若不带表达式:raise将重新引发当前作用域最后一个激活 异常

    • 若当前作用域内没有激活异常,将引发RuntimeError 提示错误
  • 否则计算表达式作为异常对象

    • 异常对象须为BaseException子类、实例
    • 若其为类,则通过不带参数的实例化该类作为异常实例
  • 异常被引发时会自动创建回溯对象,并被关联至可写 __traceback__属性

    • 可以创建异常时同时使用.with_traceback()异常方法 自定义回溯对象

      1
      raise Exception("foo occured").with_traceback(tbobj)

异常串联

  • from子句用于异常串联:其后表达式求值需要为另一个异常

    • 其将作为可写__cause__属性被关联到引发的第一个异常
    • 若引发异常未被处理,两个异常都将被打印
  • 若异常在try语句中finally子句、其他子句后、先中引发, 类似机制发挥作用,之前异常被关联到新异常的__context__ 属性

  • 异常串联可以通过在from子句中指定None显式抑制

break

break语句:终结最近外层循环、循环的可选else子句

1
break_stmt ::= "break"
  • break在语法上只出现在forwhile所嵌套代码

    • 不包括循环内函数定义、类定义所嵌套代码
  • for循环被break终结,循环控制目标保持当前值

  • break将控制流传出带有finally子句的try语句时, finally子句会先被执行,然后真正离开循环

continue

continue语句:继续执行最近外层循环的下一伦茨

1
continue_stmt ::= "continue"
  • continue在语法上只出现在forwhile所嵌套代码

    • 不包括循环内函数定义、类定义、finally子句所嵌套 代码
  • continue将控制流传出带有finally子句的try语句时, finally子句会先被执行,然后真正开始循环下一个轮次

import

import语句

1
2
3
4
5
6
7
8
import_stmt     ::=  "import" module ["as" identifier] ("," module ["as" identifier])*
| "from" relative_module "import" identifier ["as" identifier]
("," identifier ["as" identifier])*
| "from" relative_module "import" "(" identifier ["as" identifier]
("," identifier ["as" identifier])* [","] ")"
| "from" module "import" "*"
module ::= (identifier ".")* identifier
relative_module ::= "."* module | "."+
  • 基本import子句执行步骤

    • 查找模块,若有必要加载并初始化模块
    • import所处作用域的局部命名空间定义名称
    • 语句包含多个子句(逗号分隔)时,以上两个步骤将分别 对每个子句执行,如同子句被分成独立import语句
  • 默认情况下,导入的父模块中命名空间中不包含子模块属性, 即导入父模块不能直接通过属性.引用子模块

    • 有些包会在父模块中导入子模块,则初始化模块时父模块 中即有子模块属性
    • 在当前模块手动导入子模块,子模块绑定至父模块命名空间 中同名属性
  • 导入机制参见cs_python/py3ref/import_system

绑定

  • 若模块名称后带有as,则在as之后名称将直接绑定到所导入 模块

  • 若没有指定其他名称、且被导入模块为最高层级模块,则模块 名称被绑定到局部命名空间作为对所导入模块的引用

  • 若被导入模块不是最高级模块,则包含该模块的最高层级包名将 被绑定到局部命名空间作为的该最高层级包的引用,所导入模块 必须使用完整限定名称访问而不能直接访问

from子句

  • 查找from子句中指定模块,若有必要则加载并初始化模块

  • import子句中指定的每个标识符

    • 检查被导入模块是否有该名称属性
    • 若没有,尝试导入具有该名称子模块,然后再次检查 被导入(上级)模块是否具有该属性
    • 若未找到该属性,则raise ImportError
    • 否则将对该值引用存入局部命名空间,若有as子句则 使用其指定名称,否则使用该属性名称
*通配符

标识符列表为通配符*形式:模块中定义的全部公有名称都被绑定 至import语句作用域对应局部命名空间

  • 模块命名空间中__all__属性:字符串列表,指定模块 定义的公有名称

    • 其中字符串项为模块中定义、导入的名称
    • 其中中所给出的名称被视为公有、应当存在
    • 应该包含所有公有API、避免意外导出不属于API部分项
  • __all__属性未定义:则公有名称集合将包括在模块 命名空间中找到的、所有不以_开头名称

  • 通配符模式仅在模块层级允许使用,在类、函数中使用将 raise SyntaxError
相对导入

相对导入:指定导入模块时,无需指定模块绝对名称

  • 需要导入的模块、包被包含在同一包中时,可在相同顶级包中 进行相对导入,无需指明包名称

  • from子句中指定的模块、包中使用前缀点号指明需要上溯 包层级数

    • 一个前缀点号:执行导入的模块在当前包
    • 两个前缀点号:上溯一个包层级
    • 三个前缀点号:上溯两个包层级,依此类推

      1
      form ...sub_sub_pkg import mod1
  • 相对导入可以避免模块之间产生冲突,适合导入相关性强代码

    • 脚本模式(在命令行中执行.py文件)不支持相对导入
    • 要跨越多个文件层级导入,只需要使用多个.,但 PEP 328建议,相对导入层级不要超过两层

future语句

future语句:指明莫格特定模块使用在特定、未来某个python发行版 中成为标准特性的语法、语义

1
2
3
4
5
future_stmt ::=  "from" "__future__" "import" feature ["as" identifier]
("," feature ["as" identifier])*
| "from" "__future__" "import" "(" feature ["as" identifier]
("," feature ["as" identifier])* [","] ")"
feature ::= identifier
  • import __future__ [as name]:不是future语句,只是没有 特殊语义、语法限制的普通import语句
  • 用途

    • 允许模块在包含新特性发行版前使用该特性
    • 目的是使得在迁移至引入不兼容改变的python未来版本 更容易
  • future语句是针对编译器的指令

    • 在编译时被识别并做特殊对待

      • 改变核心构造语义常通过生成不同代码实现
      • 新特性可能会引入不兼容语法,如:新关键字,编译器 可能需要以不同方式解析模块
    • 编译器需要知道哪些特性名称已经定义

      • 包含未知特性的future语句将引发编译时错误
    • 直接运行时语义同其他import语句

      • 相应运行时语义取决于future语句启用的指定特性
    • 在包含future语句的环境中,通过exec()compile() 调用代码会使用future语句关联的语法、语义,此行为 可以通过compile()可选参数加以控制
  • future语句必须在靠近模块开头位置处出现,可以出现在future 语句前的行

    • 模块文档字符串
    • 注释
    • 空行
    • 其他future语句

global

global语句:声明所列出标识符将被解读为全局变量

1
global_stmt ::= "global" identifier ("," identifier)*
  • global语句是作用于整个当前代码块的声明

  • 局部作用域中给全局变量赋值必须用到global关键字

    • 仅仅是获取值无需global语句声明
    • 但自由变量也可以指向全局变量而不必声明为全局变量
  • global语句中列出的名称

    • 不能被定义为形参名
    • 不能作为for循环控制目标
    • 不能出现在类定义、函数定义、import语句、变量标注中
    • CPython:暂时未强制要求上述限制,未来可能更改
  • global是对解释器的指令,仅对与global语句同时被解析 的代码起作用

    • 包含在作为exec()参数的字符串、代码对象中global 语句不会影响exec()所在代码块
    • 反之exec()中代码也不会被调用其代码块影响
    • eval()compile()等函数同

nonlocal

nonlocal语句:使得列出的名称指向之前最近的包含作用域中 绑定的、除全局变量外的变量

1
nonlocal_stmt ::= "nonlocal" indentifier ("," identifier)*
  • nonlocal语句允许被封装代码重新绑定局部作用域以外、且非 全局(模块)作用域当中变量

    • 即nonlocal语句中列出名称,必须指向之前存在于 包含作用域中的绑定

    • nonlocal语句中列出名称不能与之前存在的局部作用域中 绑定冲突

Python 运行时服务

sys

sys:与Python解释器本身相关的组件

平台、版本

1
2
3
4
5
6
7
8
9
10
11
12
import sys
sys.platform
# 操作系统名称
sys.maxsize
# 当前计算机最大容纳“原生”整型
# 一般就是字长
sys.version
# python解释器版本号
sys.byteorder
# 平台字节序
sys.hash_info
# 数值类型hash信息

sys.xxxcheckinterval

1
2
3
4
sys.getcheckinterval()
# 查看解释器检查线程切换、信号处理器等的频率
sys.setcheckinterval(N)
# 设置解释器检查线程切换、信号处理器等的频率
  • 参数

    • N:线程切换前执行指令的数量
  • 对大多数程序无需更改此设置,但是可以用于调试线程性能

    • 较大值表示切换频率较低,切换线程开销降低,但是对事件 的应答能力变弱
    • 较小值表示切换频率较高,切换线程开销增加,对事件应答 能力提升

sys.hash_info

1
2
sys.hash_info.width
# `hash()`函数截取hash值长度

模块搜索路径

1
sys.path
  • 返回值:目录名称字符串组成的列表
    • 每个目录名称代表正在运行python解释器的运行时模块 搜索路径
    • 可以类似普通列表在运行时被修改、生效

sys.path初始化顺序

  • 脚本主目录指示器:空字符串

    • 脚本主目录是指脚本所在目录,不是os.getcwd() 获取的当前工作目录
  • PYTHONPATH环境变量

    1
    2
    # .bashrc
    export PYTHONPATH=$PYTHONPATH:/path/to/fold/contains/module
  • 标准库目录

  • .pth路径文件:在扫描以上目录过程中,遇到.pth文件会 将其中路径加入sys.path

    1
    2
    # extras.pth
    /path/to/fold/contains/module

导入模块顺序

导入模块时,python解释器

  1. 搜索内置模块,即内置模块优先级最高
  2. 从左至右扫描sys.path列表,在列表目录下搜索模块文件

嵌入解释器的钩子

1
2
3
4
5
6
sys.modules
# 已加载模块字典
sys.builtin_module_names
# 可执行程序的内置模块
sys.getrefcount()
# 查看对象引用次数

异常

1
sys.exc_info()
  • 返回值:(type, value, trackback)
    • 最近异常的类型、值、追踪对象元组
    • 处理该异常的except执行之后,sys.exc_info被恢复 为原始值
  • 追踪对象可以使用traceback模块处理

命令行参数

1
sys.argv
  • 返回值:命令行参数列表
    • 首项始终为执行脚本名称,交互式python时为空字符串
  • 参数可以自行解析,也可以使用以下标准库中模块
    • getopt:类似Unix/C同名工具
    • optparse:功能更加强大

标准流

1
2
3
4
5
6
sys.stdin
# 标准输入流
sys.stdout
# 标准输出流
sys.stderr
# 标准错误流
  • 标准流是预先打开的python文件对象

    • 在python启动时自动链接到程序上、绑定至终端
    • shell会将相应流链接到指定数据源:用户标准输入、文件

重定向

  • 可以将sys.stdinsys.stdout重置到文件类的对象,实现 python内部的普遍的重定向方式

    • 外部:cmd输入输出重定向
    • 局部:指定print参数
  • 任何方法上与文件类似的对象都可以充当标准流,与对象类型 无关,只取决于接口

    • 任何提供了类似文件read方法的对象可以指定给 sys.stdin,以从该对象read读取输入

    • 任何提供了类似文件write方法的对象可以指定给 sys.write,将所有标准输出发送至该对象方法上

  • 标准库io提供可以用于重定向的类StringIOByteIO
  • 重定向之后printinput方法将应用在重定向之后的流

stdin

1
2
3
4
5
6
7
8
stdin.read()
# 从标准输入流引用对象读取数据
input("input a word")
sys.stdin.readlines()[-1]
# 以上两行语句类似

stdin.isatty()
# 判断stdin是否连接到终端(是否被重定向)
  • 在stdin被重定向时,若需要接受用户终端输入,需要使用 特殊接口从键盘直接读取用户输入
    • win:msvcrt模块
    • linux:读取/dev/tty设备文件

退出

1
sys.exit(N)
  • 用途:当前线程以状态N退出
    • 实际上只是抛出一个内建的SystemExit异常,可以被正常 捕获
    • 等价于显式raise SystemExit
  • 进程退出参见os._exit()

sys.exitfuncs

1
sys.exitfuncs

编码

1
2
3
4
5
sys.getdefaulencoding()
# 文件内容编码,平台默认值
# 默认输入解码、输出编码方案
sys.getfilesystemencoding()
# 文件名编码,平台默认体系
  • win10中二者都是utf-8,win7中文件名编码是mbcs

sysconfig

builtins

__main__

warnings

dataclass

atexit

atexit:主要用于在程序结束前执行代码

  • 类似于析构,主要做资源清理工作

atexit.register

1
2
3
4
5
def register(
func,
*arg,
**kwargs
)
  • 用途:注册回调函数
    • 在程序退出之前,按照注册顺序反向调用已注册回调函数
    • 如果程序非正常crash、通过os._exit()退出,注册回调 函数不会被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import atexit

df func1():
print("atexit func 1, last out")

def func2(name, age):
print("atexit func 2")

atexit.register(func1)
atexit.register(func2, "john", 20)

@atexit.register
def func3():
print("atexit func 3, first out")

实现

atexit内部是通过sys.exitfunc实现的

  • 将注册函数放到列表中,当程序退出时按照先进后出方式 调用注册的回调函数,

  • 若回调函数执行过程中抛出异常,atexit捕获异常然后继续 执行之后回调函数,知道所有回调函数执行完毕再抛出异常

  • 二者同时使用,通过atexit.register注册回调函数可能不会 被正常调用

traceback

traceback.print_tb

1
2
3
4
5
6
7
8
import traceback, sys

try:
...
except:
exc_info = sys.exec_info()
print(exec_info[0], exec_info[1])
traceback.print_tb(exec_info[2])

__future__

gc

inspect

site

abc

ABCMeta

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from abc import ABCMeta, abstractmethod

class IStream(metaclass=ABCMeta):
@abstractmethod
def read(self, maxbytes=-1):
pass

@abstractmethod
def write(self, data):
pass

class SocketStream(IStream):
# 抽象类目的就是让别的类继承并实现特定抽象方法
def read(self, maxbytes=-1):
pass
def write(self, data):
pass

def serialize(obj, stream):
if not isinstance(stream, IStream):
# 抽象基类的主要用途之一:检查某些类是否为特定类型、
# 实现特定接口
raise TypeError("expect an IStream")
pass

class A(metaclass=ABCMeta):
@property
@abstract
def name(self):
pass

@name.setter
@abstractmethod
def name(self, value):
pass

@classmethod
@abstractmethod
def method1(cls):
pass

@staticmethod
@abstractmethod
def method2():
pass

# `@abstract`还能注解静态方法、类方法、`@property`
# 但是需要保证这个方法**紧靠**函数定义

用途

标准库中有很多用到抽象基类的地方

  • collections模块定义了很多和容器、迭代器(序列、映射、 集合)有关的抽象基类

    1
    2
    3
    4
    5
    6
    import collections as clt

    clt.Sequence
    clt.Iterable
    clt.Sized
    clt.Mapping
  • numbers库定义了跟数据对象:整数、浮点数、有理数有关的 基类

  • IO库定义了很多跟IO操作相关的基类

数据模型--函数决定对象

函数

用户定义函数

用户定义函数:通过函数定义创建,调用时附带参数列表

  • 函数对象支持获取、设置任意属性
    • 用于给函数附加元数据
    • 使用属性点号.获取、设置此类属性

特殊属性

  • __defaults__:有默认值参数的默认值组成元组
    • 没有具有默认值参数则为None
  • __code__编译后函数体代码对象
  • __globals__:存放函数中全局变量的字典的引用
    • 即引用函数所属模块的全局命名空间
    • 只读
  • __closure__:包含函数自由变量绑定单元的元组
    • 没有则为None
    • 只读
  • __annotations__:包含参数注释的字典
    • 字典键为参数名、return(若包含返回值)
    • 将变量名称(非私有)映射到标注值的特殊字典
    • 若该属性可写、在类或模块体开始执行时,若静态地发现 标注则被自动创建
  • __kwdefaults__keyword-only参数的默认值字典
  • 大部分可写属性会检查赋值类型
  • 自由变量:上层命名空间中变量,不包括顶级命名空间,即 全局变量不是自由变量

实例/绑定方法

实例/绑定方法:使用属性表示法调用定义在类命名空间 中的函数

  • 实例方法用于将类、类实例同可调用对象结合起来

    • 可调用对象:须为类属性,即定义在类命名空间中, 通常为用户定义函数、类方法对象
  • 通过实例访问类命名空间定义函数时创建实例/绑定方法

    • 通过示例、类访问的命名空间定义函数类型不同
      • wrapper_descriptor转换为method-wrapper
      • function转换为method
      • 通过类访问方法,得到是普通函数
    • 绑定:此时会将self作为首个参数添加到参数列表
    • 调用实例方法时,调用相应下层函数
  • 函数对象到实例方法对象的转换每次获取实例该属性时都会发生

    • 有时可将属性赋值给本地变量、调用实现性能优化
    • 非用户定义函数、不可调用对象在被获取时不会发生转换
    • 实例属性的用户定义函数不会被转换为绑定方法,仅在函数 是类的属性时才会发生
  • Py3中以上特性依赖于___getattribute__实现

属性

  • 绑定方法对象支持只读获取底层函数对象任意属性

    • 但方法属性实际保存在下层函数对象中
    • 所以不能直接设置绑定方法的方法属性,必须在下层函数 对象中显式设置,否则raise AttributeError
  • 绑定方法有两个特殊只读属性

    • m.__self__:操作该方法的类实例
    • m.__func__:底层实现该方法的函数
  • m(args,...)完全等价于m.__func__(m.__self__,args,...)

特殊元属性

  • __self__:类对象实例
  • __func__:函数对象实例
  • __doc__:方法文档,等同于__func__.__doc__
  • __name__:方法名,等同于__func__.__name__
  • __module__:定义方法所在模块名

Class Method Objects

类方法:提供了始终将类绑定为函数对象首个参数的方式

  • 对其他对象的封装,通常用于封装用户定义方法对象
  • 会改变从类、类实例获取该对象的方式
  • 用途
    • 实现自定义、多个构造器,如
      • 只调用__new__()、绕过__init__,创建未初始化 的实例
      • 反序列化对象:从字节串反序列构造符合要求的对象
  • 通过classmethod()构造器创建
  • Py3中以上特性依赖于___getattribute__实现

Static Method Objects

静态方法:提供了避免将函数对象转换为方法对象的方式

  • 对任意其他对象的封装,通常用于封装用户定义方法对象
  • 从类、类实例获取静态方法对象时,实际返回的是封装的对象, 不会被进一步转换
  • 静态方法对象自身不是可调用的,但其封装的对象通常可调用
  • 通过内置staticmethod()构造器创建
  • Py3中以上特性依赖于___getattribute__实现

内置函数、方法

  • 内置函数:对C函数的外部封装
  • 内置方法:内置函数另一种形式,(类似实例方法)隐式传入 当前实例作为C函数额外参数
  • 包括以下两种类型
    • builtin_function_or_method
    • wrapper_descriptor
  • 参数数量、类型由C函数决定
  • 内置方法由支持其的类型描述

特殊元属性

  • __self__<module 'builtins' (built-in)>
  • __doc__:函数/方法文档
  • __name__:函数/方法名
  • __module__:所在模块名

说明

  • 函数是描述器,函数类function实现有__get__方法
  • function.__get__即将函数首个参数进行绑定,返回绑定方法
  • 参见cs_python/py3ref/cls_special_methods

Generator Functions

生成器函数:使用yield语句的函数、方法称为生成器函数

  • 生成器函数调用时返回生成器迭代器对象,控制执行函数体
  • yield表达式参见cs_python/py3ref/expressions

Generator-Iterator

生成器迭代器:生成器函数执行得到的迭代器

  • 实现有迭代器协议__next__的迭代器类实例不同于 生成器迭代器,其仅实现迭代

    • 迭代执行过程即__next__函数调用,重入(状态维护) 由类负责
    • 无不同迭代状态区别
    • 不会自动获得.send.throw.close等方法
  • 或者说生成器迭代器是:利用yield表达式对迭代器的的简化 实现,并预定义.send.throw.close方法

  • 迭代器协议参见cs_python/py3ref/cls_special_methods

执行过程

  • 其某方法被调用时,生成器函数开始执行
  • 执行到第一个yield表达式被挂起,保留所有局部状态
    • 局部变量当前绑定
    • 指令指针
    • 内部求值栈
    • 任何异常处理状态
  • 返回expression_list
  • 调用生成器某方法,生成函数继续执行
  • 执行return、函数体执行完毕将raise StopIteration
  • 生成器表达式、yield表达式参见 cs_python/py3ref/expressions

生成器迭代器状态

生成器在声明周期中有如下4中状态

  • "GEN_CREATED":等待执行
  • "GEN_RUNNING":正在执行,只有多线程中才能看到
  • "GEN_SUSPENDED":在yield表达式处挂起状态
  • "GEN_CLOSED":关闭状态
  • 可通过inspect.getgeneratorstate()方法查看

__next__

1
2
def(pre) generator.__next__():
pass
  • 用途:开始生成器函数执行、或从上次执行yield表达式处恢复 执行

    • 生成器函数通过__next__方法恢复执行时,yield表达式 始终取值为None
    • 执行至下个yield表达式
  • 返回值:生成器迭代器产生的下个值

    • yield表达式中expression_list
    • 若生成器没有产生下个值就退出,raise StopIteration
  • 此方法通常隐式通过for循环、next()函数调用

send

1
2
def(pre) generator.send(value):
pass
  • 用途:恢复生成器函数执行并向其“发送”值value

    • value参数作为yield表达式的结果
    • 执行至下个yield表达式
  • 返回值:生成器迭代器产生的下个值

    • yield表达式中expression_list
    • 若生成器没有产生下个值就退出,raise StopIteration
  • 说明

    • 若生成器中包含子迭代器,send传参数值将被传递给 下层迭代器,若子迭代器没有合适接收方法、处理,将 raise AttributeErrorraise TypeError
    • .send方法参数为None时实际不会有传递参数行为
      • 调用.send()启动生成器时,必须以None作为调用 参数,因为此时没有可以接收值的yield表达式
      • 子迭代器中没有处理参数时,.send(None)也能正常 工作

throw

1
2
def(pre) generator.throw(type[, value[, traceback]]):
pass
  • 用途:在生成器暂停位置处引发type类型异常

    • 若异常被处理:则执行直至下个yield表达式
    • 若生成器函数没有捕获传入异常、或引发另一个异常:异常 会被传播给调用者
  • 返回值:返回生成器产生的下个值(若异常被处理)

    • 若生成器没有产生下个值就退出,raise StopIteration
  • 说明

    • 若生成器中包含子迭代器,throw传入异常将被传递给 子迭代器
    • 调用throw启动生成器,会在生成器函数开头引发错误

close

1
2
def(pre) generator.close():
pass
  • 用途:在生成器函数暂停处raise GeneratorExit
    • 若之后生成器函数正常退出、关闭、引发GeneratorExit (生成器中未捕获该异常):则关闭生成器并返回调用者
    • 若生成器继续产生值:则raise RuntimeError
    • 若生成器引发其他异常:则传播给调用者
    • 若生成器由于异常、或正常而退出:则无操作

Yield表达式—调用外部函数

  • 生成器函数执行yield表达式类似调用外部函数
  • 当前函数栈保留当前状态、挂起
  • 执行yield表达式“完毕”后,“返回”到调用处
  • yield表达式“返回值”取决于生成器恢复执行所调用方法
    • .__next__fornext()调用,返回None
    • .send():返回.send()参数
  • 此时整体类似于
    • 主调函数调用生成器函数
    • 生成器函数调用yield表达式

生成器函数—协程

  • 生成器函数类似协程,也被称为semi-coroutine,是协程子集
  • 相似点
    • yield多次,有多个入口点
    • 执行可以被挂起
    • 可在恢复执行时传递参数控制执行
  • 不同点
    • yield后控制权总是转移给生成器迭代器调用者,生成器 函数不能控制yield后继续执行的位置 (yield表达式仅仅传递值给父程序,并不是指定需要 跳转的、平等的协程)

生成器函数—try

  • try结构中任何位置都允许yield表达式
  • 若生成器在销毁之前没有恢复执行(引用计数为0、被垃圾 回收),try结构中的yield表达式挂起可能导致finally 子句执行失败

  • 此时应由运行该异步生成器的事件循环、或任务调度器负责调用 生成器-迭代器close()方法,从而允许任何挂起的finally 子句得以执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def echo(value=None):
print("execution start")
try:
while True:
try:
value = (yield value)
except Exception as e:
value = e
finally:
print("clean up when gen.close() is called")

if __name__ == "__main__":
gen = echo(1)
print(next(gen)) # 1
print(next(gen)) # None
print(gen.send(2)) # 2
print(gen.throw(TypeError, "spam")) # TypeError('spam',)
gen.close() # clean up...

Coroutine Function

协程函数:使用async def定义的函数、方法

  • 调用时返回一个coroutine对象
  • 可能包含await表达式、async withasync for语句
    • 即协程函数能在执行过程中挂起、恢复
  • 协程参见cs_program/#todo

Coroutine Objects

协程对象:属于awaitable对象

  • 协程执行:调用__await__,迭代其返回值进行控制

    • 协程结束执行、返回时,迭代器raise StopIteration, 异常的value属性将指向返回值
    • 等待协程超过一次将raise RuntimeError
  • 若协程引发异常,其会被迭代器传播

    • 协程不应该直接引发未处理的StopIteration
  • 可等待对象参见cs_python/py3ref/cls_special_methods

send

1
2
def(pre) coroutine.send(value):
pass
  • 用途:开始、恢复协程执行

    • value is None:相当于等待__await__()返回迭代器 结果下一项
    • value is not None:此方法将委托给导致协程挂起的 迭代器的send()方法
  • 返回值:返回值、异常等同对__await__()返回值迭代结果

throw

1
2
def(pre) coroutine.throw(type[, value[, traceback]]):
pass
  • 用途:在协程内引发指定异常

    • 此方法将委托给导致协程内挂起的迭代器的throw()方法 ,若其存在
    • 否则异常在挂起点被引发
  • 返回值:返回值、异常等同对__await__()返回值迭代结果

    • 若异常未在协程内被捕获,则传回给主调者

close

1
2
def(pre) coroutine.close():
pass
  • 用途:清理自身并退出
    • 若协程被挂起,此方法先被委托给导致该协程挂起的迭代器 的.close()方法,若其存在
    • 然后在挂起点raise GeneratorExit,使得协程立即清理 自身
    • 最后协程被标记为已结束执行,即使未被启动
    • 协程对象将被销毁时,会被自动调用

Asynchronous Generator Functions

异步生成器函数:包含yield语句、使用async def定义函数、 方法(即在协程中)

  • 返回异步生成器迭代器对象,控制执行函数体
  • yield from表达式在异步生成器函数中使用将引发语法错误

Asynchronous Generator-Iterator

异步生成器迭代器

  • 异步体现:async def定义协程函数中可以使用异步代码

  • 异步生成器迭代器方法返回的可等待对象执行同一函数栈

    • 可等待对象被await运行时才会执行异步函数体
    • 类似普通生成器迭代器,执行到yield表达式挂起
    • 则连续返回多个可等待对象可以乱序await
    • 可以视为返回待执行函数体
  • 异步生成器迭代器执行过程、yield表达式、try结构、同异步 迭代器协议关联, 均参见普通生成器迭代器

  • 异步迭代器协议参见cs_python/py3ref/cls_special_methods

  • 异步生成器函数使用yield from语句将引发语法错误

最终化处理#todo

处理最终化:事件循环应该定义终结器函数

  • 其接收异步生成器迭代器、且可能调用aclose()方法并执行 协程
  • 终结器可以通过调用sys.set_asyncgen_hooks()注册
  • 首次迭代时,异步生成器迭代器将保存已注册终结器以便最终化 时调用

__anext__

1
2
async def(pre) coroutine agen.__anext__():
pass
  • 用途:返回可等待对象,可等待对象运行时开始、恢复异步 生成器函数执行

    • 异步生成器函数通过__anext__方法恢复执行时,返回的 可等待对象中yield表达式始终取值为None
  • 可等待对象返回值:返回异步生成器的下个值

    • 执行至下个yield表达式,返回expression_list
    • 若异步生成器没有产生下个值就退出,则 raise StopAsyncIteration
  • 此方法通常由async for异步循环隐式调用

asend

1
2
async def(pre) coroutine agen.asend(value):
pass
  • 用途:返回可等待对象,可等待对象运行时开始、恢复异步 生成器函数执行,并向其“发送”值value

    • value参数作为yield表达式的结果
    • 执行至下个yield表达式
  • 返回值:异步生成器产生的下个值

    • yield表达式中expression_list
    • 若生成器没有产生下个值就退出,则 raise StopAsyncIteration
  • 说明

    • 调用.asend()启动生成器时,必须以None作为调用参数

athrow

1
2
async def(pre) coroutine agen.athrow(type[, value[, traceback]]):
pass
  • 用途:返回可等待对象,可等待对象运行时将在异步生成器函数 暂停位置处引发type类型异常

    • 若异常被处理:则执行直至下个yield表达式
    • 若异步生成器函数没有捕获传入异常、或引发另一个异常: 可等待对象运行时异常会被传播给调用者
  • 返回值:返回生成器产生的下个值(若异常被处理)

    • 若生成器没有产生下个值就退出,则 raise StopAsyncIteration
  • 说明

    • 调用athrow启动异步生成器,会在函数开头引发错误

aclose

1
2
async def(pre) coroutine agen.aclose():
pass
  • 用途:返回可等待对象,可等待对象运行时在异步生成器函数 暂停处raise GeneratorExit
    • 若异步生成器函数正常退出、关闭、引发GeneratorExit (生成器中未捕获该异常):则运行可等待对象将 raise StopIteration???
    • 后续调用异步生成器迭代器方法返回其他可等待对象:则 运行可等待对象将raise StopAsyncIteration
    • 若异步生成器函数产生值:则运行可等待对象将 raise RuntimeError
    • 若异步生成器迭代器已经异常、正常退出,则后续调用 aclose方法将返回无行为可等待对象

数据持久化

DBM

DBM文件:python库中数据库管理的标准工具之一

  • 实现了数据的随机访问
    • 可以使用键访问存储的文本字符串
  • DBM文件有多个实现
    • python标准库中dbm/dbm.py

使用

  • 使用DBM文件和使用内存字典类型非常相似
    • 支持通过键抓取、测试、删除对象

pickle

  • 将内存中的python对象转换为序列化的字节流,可以写入任何 输出流中
  • 根据序列化的字节流重新构建原来内存中的对象
  • 感觉上比较像XML的表示方式,但是是python专用
1
2
3
4
5
6
7
import pickle
dbfile = open("people.pkl", "wb")
pickle.dump(db, dbfile)
dbfile.close()
dbfile = open("people.pkl", "rb")
db = pickle.load(dbfile)
dbfile.close()
  • 不支持pickle序列化的数据类型
    • 套接字

shelves

  • 就像能必须打开着的、存储持久化对象的词典
    • 自动处理内容、文件之间的映射
    • 在程序退出时进行持久化,自动分隔存储记录,只获取、 更新被访问、修改的记录
  • 使用像一堆只存储一条记录的pickle文件
    • 会自动在当前目录下创建许多文件
1
2
3
4
5
6
import shelves
db = shelves.open("people-shelves", writeback=True)
// `writeback`:载入所有数据进内存缓存,关闭时再写回,
// 能避免手动写回,但会消耗内存,关闭变慢
db["bob"] = "Bob"
db.close()

copyreg

marshal

sqlite3

二进制数据服务

struct

struct模块:用于打包、拆包Cstruct格式二进制数据

  • 使用python字节串存储Cstruct数据
  • 需要给出格式声明,指定二进制中存储格式

格式说明

字节序、对齐

格式符 字节序(大、小端) 类型大小 字段对齐方式
@(缺省值) 原生字节序 原生大小 原生对齐
= 原生字节序 标准大小 标准对齐
< lb-endian 标准大小 标准对齐
> bl-endian 标准大小 标准对齐
! >
  • 原生对齐:C对齐方式,字段起始位置须为其长度整数倍
  • 标准对齐:按字节对齐,无需使用0补齐

类型

Format C Type Python Bytes
x pad byte no value 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long long 4
q long long long 8
Q unsigned long long long 8
f float float 4
d double float 4
c char str of length1 1
b signed char bytes of length1 1
B unsigned char bytes of length1 1
s char[] str 1
p pascal string,带长度 str NA
n ssize_t integer NA
N size_t integer NA
P void * 足够容纳指针的整形 NA
  • 在类型符前添加数字可以指定类型重复次数

  • 字符、字符串类型实参须以字节串形式给出

    • 字符:必须以长度为1字节串形式给出,重复须对应多参数
    • 字符串:可以是任意长度字节串,重复对应单个字符串
      • 长于格式指定长度被截断
      • 短于格式指定长度用\0x00补齐
  • python按以上长度封装各类型,但C各类型长度取决于平台, 以上近视64位机器最可能对应C类型

    • 非64位机器long long类型大部分为4bytes
  • 用途

    • 封装、解压数据
    • reinterpret_cast类型转换

Struct

1
2
3
class Struct:
def __init__(self, fmt):
pass
  • 用途:根据指定格式fmt编译Sturct对象
    • Sturct对象可以调用以下方法,无需再次指定fmt

Pack

1
2
3
4
def struct.pack(fmt, v1, v2,...) -> bytes:
pass
def struct.pack_into(fmt, buffer, offset , v1, v2, ...):
pass
  • pack:按照指定格式fmt封装数据为字节串
  • pack_into:将字节串写入buffer指定偏移offset

Unpack

1
2
3
4
5
6
def struct.unpack(fmt, buffer) -> (v1, v2,...):
pass
def struct.unpack(fmt, buffer, offset=0) -> (v1, v2, ...):
pass
def struct.iter_unpack(fmt, buffer) -> iterator(v1, v2,...):
pass
  • unpack:按照指定格式fmt拆包字节串buffer
  • unpack_from:从偏移offset处开始拆包
  • iter_unpack:迭代解压包含多个fmtbuffer

calsize

1
2
def struct.calsize(fmt) -> integer:
pass
  • 用途:计算封装指定格式fmt所需字节数

codecs