看书 Python 源码分析笔记 (三) dict

今天继续从第五章开始学习.

第五章 Python 中的 Dict 对象

这是 python 提供的关联容器, 即 key,value 对映射, python 使用 hash table 的方式实现此关联容器.
(也有别的实现方式, 如 STL 中使用 RB-tree 红黑树等).

由于不同的(动态)语言实现此类结构的策略有不同, 我们可以仔细看看 python 的实现有什么特点.

书中关于 hash-table 的基本知识略. (大概 python 使用开放定址法解决冲突?)

PyDictObject

定义: 将关联容器中的一个 <键,值> 对 叫做 entry, 或 slot. 在 python 中该结构为:

// 定义于 dictobject.h 文件中.
struct PyDictEntry {
  size_t me_hash;       // 缓存 key 的哈希值.
  PyObject *me_key;     // key,value 对
  PyObject *me_value;
}

此结构中提供 me_hash 用空间换时间, 缓存 key 的 hash 值.
根据 me_key, me_value 的值有三种状态组合:
1. key==null, value==null, 这是一个空的 entry, 可存放新值.
2. key!=null, value!=null, 这是一个 active 状态的 entry, 即有 key-value 对.
2. key=dummy(特殊键值) 这是一个 Dummy 态(墓碑). 为保证冲突检测链有效.

理论见算法书各种 hash 实现技术. 在 python 中实现结构如下:

class PyDictObject {
  HEAD 部分;
  size_t ma_fill;  // 元素个数. active+dummy
  size_t ma_used;  // 元素个数. active
  size_t ma_mask;
  PyDictEntry *ma_table;  // 指向 entry 数组
  PyDictEntry *(*ma_lookup)(...);
  PyDictEntry ma_smalltable[MINSIZE=8];
}

在尾部有一个 8 个entry 的小数组, 数字 8 是大量实验得出的最佳值. 它取得某种好的平衡.
如果 dict 中元素数量少的时候, ma_table 指向内部的 ma_smalltable; 否则指向额外分配的内存区域.

PyDictObject 的创建和维护

方法 PyDict_New() 用于创建一个新 dict 对象. (我们可将其看做是构造函数)
同样内部有一个 dict 对象的缓冲池, 如同 list 对象的.

搜索函数 ma_lookup() 被初始化为 lookdict_string, 它实现 dict 对象的搜索策略.

Python 提供两种搜索策略, lookdict 和 lookdict_string, 由于 string 常用, 故而选择为默认的.
搜索算法(略微伪代码一点):

dictentry *lookdict(*dict, *key, hash) {
  // 1. 根据 hash, dict 大小(mask) 计算所在 entry 位置.
  int idx = hash & dict.ma_mask;  // 这里 mask 形式为 2^N-1, 故而可以用 & 计算.
  // 这里 entries = dict.ma_table;
  dictentry *entry = &entries[idx];

  // 2. 如果 idx 对应 entry 就是 key, 则找到返回.
  if (entry.key == (null || key))
    return entry;  // 找到了.

  // 3. 如果是 dummy 的, 则算是找到了一个 free-slot (可用位置), 用于插入新值.
  if (entry.key == dummy)
    free_slot = entry;
  else {
    if (entry.hash == hash && entry.key == key) ... 
  }

  
  // 4. 上面检测到了冲突. 今计算下一个探测位置, 检查是否可用...
  //  探测地址函数似乎是 next_i = i*5 + 1 + 某个递减的因子; 
  //  注释中说参见 Knuth 的算法书, base on Algorithm D from Knuth Vol.3, Sec. 6.4.
}

源码中有一个 dictnotes.txt 文件, 似乎描述 dict 对象的优化思路等.

伪代码中 entry.key == key 是一种简化写法, 实际 python 对于什么是 == (相同) 是表示值相同.
为了比较值相同, 用简单的 `==' 是远远不够的, 实际代码中使用 PyObject_RichCompareBool() 来完成.

此函数实际承担两种任务, 分别是 1.查找(找到指定key对应的entry,或未找到); 2.插入(找到key 的插入位置entry,
如果有则返回覆盖, 没有则返回可用位置).

这样, 向 hash 中插入 key,value 对时, 就是先用 look 查找, 结果可能是 1.找到, 则覆盖; 2.未找到, 则新添加 entry.

同样, 删除操作也是先 look, 找到则删除, 未找到则返回 失败即可.

由于这是一个典型的 hash 实现, 理解理论的最好办法是看算法书, 所以细节的就不研究那么多了.

PyDictObject 对象缓冲池

类似于 list 对象, dictobject.c 内部使用一个 free_dicts[80] 数组维护一个简单的缓冲池.

 

===============================================================

第六章 最简单的 Python 模拟 -- Small Python

用前面几章介绍的对象, 做出一个简单的 python, 这个模拟作者称之为 small python.

看书, 作者大量简化原 python 对象代码, 以实现一个非常简单的 python. 或者叫简陋的 python.

略去.

下面作者准备带我们去探索: 字节码, 虚拟机, 条件判断, 函数, 类, 异常等 第三部分, 所以我们也换一篇学习笔记...

你可能感兴趣的:(看书 Python 源码分析笔记 (三) dict)