本篇文章中所涉及到的词典结构,比如词典中的dummy成员,empty成员,有效成员之类的概念,请参考上一篇文章的内容。在英文教程里,介绍了几个和词典相关的内建脚本函数:cmp,len,str,type。下面就先对这几个内建函数一一进行介绍...

    页面导航: 英文教程的下载地址:

    本篇文章是根据英文教程《Python Tutorial》来写的学习笔记。该英文教程的下载地址如下:

    百度盘地址:http://pan.baidu.com/s/1c0eXSQG

    DropBox地址:点此进入DropBox链接

    Google Drive:点此进入Google Drive链接

    这是学习笔记,不是翻译,因此,内容上会与英文原著有些不同。以下记录是根据英文教程的第十二章来写的。(文章中的部分链接,包括下载链接,可能需要通过代理访问!)

    本篇文章也会涉及到Python的C源代码,这些C源码都是2.7.8版本的。想使用gdb来调试python源代码的话,就需要按照前面"Python基本的操作运算符"文章中所说的,使用configure --with-pydebug命令来重新编译安装python。

    如果Python的官方网站取消了Python-2.7.8.tgz的源码包的话,可以在以下两个链接中下载到:

    DropBox:Python-2.7.8.tgz的DropBox网盘链接

    Google Drive:Python-2.7.8.tgz的Google Drive网盘链接

    本篇文章中所涉及到的词典结构,比如词典中的dummy成员,empty成员,有效成员之类的概念,请参考上一篇文章的内容。

内建脚本函数:

    在英文教程里,介绍了几个和词典相关的内建脚本函数:cmp,len,str,type。下面就先对这几个内建函数一一进行介绍。

cmp(dict1, dict2):

    先来看个简单的例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {123:345, 234:456}
>>> dict2 = {345:567, 456:789}
>>> cmp(dict1,dict2)
....................................................

Breakpoint 1, builtin_cmp (self=0x0, args=0xb7d3a8f4)
    at Python/bltinmodule.c:427
427	    if (!PyArg_UnpackTuple(args, "cmp", 2, 2, &a, &b))
....................................................
Breakpoint 2, dict_compare (a=0xb7d44df4, b=0xb7ca2494)
    at Objects/dictobject.c:1799
1799	    if (a->ma_used < b->ma_used)
....................................................
(gdb) c
Continuing.
-1
>>> 


    可以看到,cmp内建脚本函数,在内部最终会通过dict_compare这个C函数去完成具体的比较操作,该函数定义在Objects/dictobject.c文件中:

static PyObject *
characterize(PyDictObject *a, PyDictObject *b, PyObject **pval)
{
    PyObject *akey = NULL; /* smallest key in a s.t. a[akey] != b[akey] */
    PyObject *aval = NULL; /* a[akey] */
    Py_ssize_t i;
    int cmp;

    // dict_compare会调用此函数来得到
    // 词典a相对于词典b的最小的key,并
    // 获取到该key对应的value。
    // 这对key-value也可以看作是a相对于
    // b的最小成员。
    // (该key必须要么不存在于词典b中,
    // 要么与b中的value不相等)
    // 假设a = {1:2, 2:3, 3:4, 4:5, 6:7},
    // b = {1:2, 2:3, 3:4, 4:6, 7:8},
    // 由于1:2, 2:3, 3:4这三个成员在a与b
    // 中都存在,因此,该函数在获取相对最小key时,
    // 就会跳过这三个成员,这样,
    // 词典a中就剩下4:5, 6:7了,这两个成员
    // 中最小的key是4,因此,a相对于b的最小key
    // 就是4,结果就会将4返回,同时将该key对应的
    // value即5作为pval参数的值。
    // dict_compare函数在比较词典a与b时,
    // 会调用此函数两次,第一次得到a相对于
    // b的最小key,第二次得到b相对于a的
    // 最小key,b相对于a的最小成员是4:6。
    // dict_compare在得到这两个词典的相对最小
    // 成员后,会对这两个最小成员进行
    // 比较,并将比较结果作为这两个词典的比较结果,
    // 本例中,a相对于b的最小成员是4:5,
    // b相对于a的最小成员是4:6,
    // 在相对最小成员的key值相等的情况下,
    // 就以该key对应的value的比较结果作为最终
    // 比较结果,4:5的value是5,4:6的value是6,
    // 5小于6,因此词典a就小于词典b了。
    // 如果相对最小成员的key值不相等,
    // 则直接以key的比较结果作为词典的比较结果,
    // 例如:a = {1:2, 2:3, 3:4, 5:9, 6:7},
    // b = {1:2, 2:3, 3:4, 4:6, 7:8},
    // a相对于b的最小成员是5:9,
    // b相对于a的最小成员是4:6,
    // 最小成员的key中,5大于4,因此词典a就大于词典b了。

    // 通过for循环,查找词典a相对于词典b
    // 的最小的key,并得到该key对应的value。
    for (i = 0; i <= a->ma_mask; i++) {
        PyObject *thiskey, *thisaval, *thisbval;
        // 跳过词典a中的dummy与empty成员。
        // 只对词典中的有效成员进行查找。
        if (a->ma_table[i].me_value == NULL)
            continue;
        // 下面的thiskey变量用于存储查找过程中各
        // 有效成员的key,而下面的akey变量中
        // 则用于存储查找到的相对最小的key。
        // akey并不是一次就可以确定好的,
        // 它需要在for循环中通过对每个成员的key
        // 进行比较判断,才能确定最终的akey值。
        // 例如:a = {1:2, 2:3, 3:4, 5:9, 6:7},
        // b = {1:2, 2:3, 3:4, 4:6, 7:8},
        // 那么可能第一次会从a中获取到1:2的key,
        // 然后将1:2的key即1,存储到thiskey变量中,
        // 第一次执行时,akey为NULL,下面的if
        // 语句不会被执行,if之后的语句会先对
        // thiskey进行判断,也就是判断thiskey
        // 是否存在于词典b中,如果thiskey存在于
        // 词典b中,就再判断thiskey在词典a中的
        // value是否等于词典b中的value,
        // 只有当thiskey不存在于词典b中,或者
        // 当thiskey对应的词典a中的value与词典b
        // 中的value不相等时,thiskey才可以作为
        // akey,否则就会跳过该thiskey。
        // 因此,1:2, 2:3, 3:4这三个成员对应的
        // thiskey都会被跳过,
        // 接着,5:9的thiskey即5会被设置为akey,
        // 再接着,6:7的thiskey即6会与akey即5进行
        // 比较,只有thiskey小于akey时,该
        // thiskey才可以取代原来的akey,
        // 作为新的akey,此例中thiskey即6大于akey,
        // 因此,akey还是5,在所有成员都比较完后,
        // akey即5会作为最终的相对最小的key,
        // 并作为此函数的结果返回。同时akey对应
        // 的value即9会作为pval参数的值。这样就得到
        // 词典a相对于词典b的最小成员5:9了。
        thiskey = a->ma_table[i].me_key;
        Py_INCREF(thiskey);  /* keep alive across compares */
        // 如果akey存在,则将thiskey与akey进行比较。
        if (akey != NULL) {
            cmp = PyObject_RichCompareBool(akey, thiskey, Py_LT);
            if (cmp < 0) {
                Py_DECREF(thiskey);
                goto Fail;
            }
            // 如果akey小于thiskey,
            // 则跳过该thiskey。
            // 只有当thiskey小于akey时,
            // 该thiskey才可以作为新的akey。
            if (cmp > 0 ||
                i > a->ma_mask ||
                a->ma_table[i].me_value == NULL)
            {
                /* Not the *smallest* a key; or maybe it is
                 * but the compare shrunk the dict so we can't
                 * find its associated value anymore; or
                 * maybe it is but the compare deleted the
                 * a[thiskey] entry.
                 */
                Py_DECREF(thiskey);
                continue;
            }
        }

        // 得到thiskey对应的value
        /* Compare a[thiskey] to b[thiskey]; cmp <- true iff equal. */
        thisaval = a->ma_table[i].me_value;
        assert(thisaval);
        Py_INCREF(thisaval);   /* keep alive */
        thisbval = PyDict_GetItem((PyObject *)b, thiskey);
        // 如果thiskey不存在于词典b中,
        // 则该thiskey可以用于取代原来的akey。
        if (thisbval == NULL)
            cmp = 0;
        else {
            // 如果thiskey存在于词典b中,
            // 则将thiskey在词典a中的value与
            // thiskey在词典b中的value进行比较,
            // 只有当thiskey在两个词典中的
            // value不相等时,
            // 该thiskey才可以用于取代原来的akey。
            /* both dicts have thiskey:  same values? */
            cmp = PyObject_RichCompareBool(
                                    thisaval, thisbval, Py_EQ);
            if (cmp < 0) {
                Py_DECREF(thiskey);
                Py_DECREF(thisaval);
                goto Fail;
            }
        }
        // 经过了上面几个判断工作后,
        // 当thiskey小于akey时(或者akey没被设置时),
        // 且thiskey不存在于词典b中,
        // 或者thiskey在两个词典中的
        // value不相等时,就将该thiskey
        // 作为新的akey。
        if (cmp == 0) {
            /* New winner. */
            Py_XDECREF(akey);
            Py_XDECREF(aval);
            akey = thiskey;
            aval = thisaval;
        }
        else {
            Py_DECREF(thiskey);
            Py_DECREF(thisaval);
        }
    }
    // 将akey对应的value作为pval参数的值。
    *pval = aval;
    return akey;

Fail:
    Py_XDECREF(akey);
    Py_XDECREF(aval);
    *pval = NULL;
    return NULL;
}

static int
dict_compare(PyDictObject *a, PyDictObject *b)
{
    PyObject *adiff, *bdiff, *aval, *bval;
    int res;

    // 先比较两个词典的有效长度,
    // 即哪个词典的有效成员数多,
    // 则哪个词典就大。
    // 例如:a = {1:2, 2:3, 3:4}
    // b = {1:2, 2:3},
    // 此例中,a的有效成员数为3,
    // b的有效成员数为2,a的有效长度
    // 大于b,因此,a就大于b了。
    // 此外,当词典a大于词典b时,该函数会返回1,
    // 反之,如果词典a小于词典b,则返回-1,
    // 如果词典a等于词典b,则返回0
    /* Compare lengths first */
    if (a->ma_used < b->ma_used)
        return -1;              /* a is shorter */
    else if (a->ma_used > b->ma_used)
        return 1;               /* b is shorter */

    /* Same length -- check all keys */
    bdiff = bval = NULL;
    // 先通过上面的characterize函数得到
    // 词典a相对于词典b的最小成员的key及value。
    adiff = characterize(a, b, &aval);
    // 如果没找到相对的最小成员,
    // 则说明要么发生了错误,
    // 要么就是词典a中所有的成员都存在于
    // 词典b中,也就是这两个词典相等。
    // 例如:a = {1:2, 2:3, 3:4},
    // b = {1:2, 2:3, 3:4},
    // 这两个词典的有效长度相同,且
    // 词典a里的三个有效成员1:2, 2:3, 3:4都存在于
    // 词典b中,此时,这两个词典就相等。
    if (adiff == NULL) {
        assert(!aval);
        /* Either an error, or a is a subset with the same length so
         * must be equal.
         */
        res = PyErr_Occurred() ? -1 : 0;
        goto Finished;
    }
    // 再得到词典b相对于词典a的最小成员的key
    // 及value。
    bdiff = characterize(b, a, &bval);
    // 如果找不到词典b相对于词典a的最小成员,那么
    // 就说明发生了错误。
    if (bdiff == NULL && PyErr_Occurred()) {
        assert(!bval);
        res = -1;
        goto Finished;
    }
    res = 0;
    if (bdiff) {
        /* bdiff == NULL "should be" impossible now, but perhaps
         * the last comparison done by the characterize() on a had
         * the side effect of making the dicts equal!
         */
        // 先对最小成员的key进行比较,
        // 并将key的比较结果作为词典的比较结果。
        res = PyObject_Compare(adiff, bdiff);
    }
    // 如果两个最小成员的key值相等,
    // 则对这两个key对应的value进行比较,
    // 并将value的比较结果作为最终的比较结果。
    if (res == 0 && bval != NULL)
        res = PyObject_Compare(aval, bval);

Finished:
    Py_XDECREF(adiff);
    Py_XDECREF(bdiff);
    Py_XDECREF(aval);
    Py_XDECREF(bval);
    return res;
}


    可以看到,dict_compare函数会先比较两个词典的有效成员数,哪个词典的有效成员数多,哪个词典就大。在有效成员数相同的情况下,如果不存在不同的成员,则两个词典相等。如果存在不同的成员(也就是key不相同或者value不相同的成员),则从不同的成员里筛选出key值最小的成员,并以最小成员的比较结果作为词典的比较结果。如下图所示:


图1

    在比较最小成员时,如果key值不同,则以key的比较结果作为最终的比较结果。如果key值相同,则以对应的value的比较结果作为最终的结果。上图中4:5与4:6的key值都是4,因此,就以value的比较结果来作为词典的比较结果,5小于6,因此,词典a就小于词典b了。

len(dict):

    先来看个len脚本函数的例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {1:2, 2:3, 3:4, 4:5, 5:6}
>>> len(dict1)
....................................................

Breakpoint 1, builtin_len (self=0x0, v=0xb7d44df4) at Python/bltinmodule.c:1317
1317	    res = PyObject_Size(v);
(gdb) c
Continuing.

Breakpoint 2, dict_length (mp=0xb7d44df4) at Objects/dictobject.c:1160
1160	    return mp->ma_used;
(gdb) p *mp
$1 = {_ob_next = 0xb7ca1ae0, _ob_prev = 0xb7ca1114, ob_refcnt = 2, 
  ob_type = 0x81c4fe0, ma_fill = 5, ma_used = 5, ma_mask = 7, 
  ma_table = 0xb7d44e18, ma_lookup = 0x808f7a1 <lookdict>, ma_smalltable = {{
      me_hash = 0, me_key = 0x0, me_value = 0x0}, {me_hash = 1, 
      me_key = 0x8207b5c, me_value = 0x8207b48}, {me_hash = 2, 
      me_key = 0x8207b48, me_value = 0x8207b34}, {me_hash = 3, 
      me_key = 0x8207b34, me_value = 0x8207b20}, {me_hash = 4, 
      me_key = 0x8207b20, me_value = 0x8207b0c}, {me_hash = 5, 
      me_key = 0x8207b0c, me_value = 0x8207af8}, {me_hash = 0, me_key = 0x0, 
      me_value = 0x0}, {me_hash = 0, me_key = 0x0, me_value = 0x0}}}
(gdb) c
Continuing.
5
>>> 


    可以看到,len内建脚本函数,最终会通过dict_length这个C函数去完成具体的操作,该函数也定义在Objects/dictobject.c文件里,该函数其实就是将词典对象的ma_used字段作为结果返回,该字段在上一篇文章里已经介绍过了,它存储了词典中包含的有效成员数。上例中,由于dict1包含了5个有效成员,因此,len(dict1)执行后就返回了结果5

str(dict):

    以下是str脚本函数的简单例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {1:2, 3:4, 5:6}
>>> str(dict1)
....................................................

Breakpoint 3, dict_repr (mp=0xb7ca2a34) at Objects/dictobject.c:1082
1082	    PyObject *s, *temp, *colon = NULL;
(gdb) c
Continuing.
'{1: 2, 3: 4, 5: 6}'
>>> 


    str用于将参数转为字符串对象。从上面的调试中可以看到,在将词典对象转为字符串对象时,最终会通过Objects/dictobject.c文件里的dict_repr函数去完成具体的转换工作:

static PyObject *
dict_repr(PyDictObject *mp)
{
    Py_ssize_t i;
    PyObject *s, *temp, *colon = NULL;
    PyObject *pieces = NULL, *result = NULL;
    PyObject *key, *value;

    // 先将当前词典对象加入到系统指定的列表中,
    // 那么如果出现递归转换当前对象的情况时,
    // 就只会返回'{...}'的字符串对象。
    // 这样就可以有效的防止无穷递归的情况发生。
    // 如下面这个例子:
    // >>> dict1 = {}
    // >>> dict1['self'] = dict1
    // >>> str(dict1)
    // "{'self': {...}}"
    // 上面例子中,词典dict1将自己作为'self'的
    // value,由于dict1中包含了自己,在将
    // dict1转为字符串对象时,就会出现递归转换
    // 的情况,当递归调用本函数来转换同一词典时,
    // 'self'对应的dict1就会用{...}来代替。
    // 下面的Py_ReprEnter在将mp加入到系统列表中后,
    // 在本函数的最后,会通过Py_ReprLeave将mp
    // 从系统列表里给移除掉。这样就不会妨碍
    // 该对象的下一次的转换工作了。
    i = Py_ReprEnter((PyObject *)mp);
    // 如果i不为0,就说明当前词典对象已经存在于
    // 系统列表中了,也就说明当前函数是在递归转换
    // 同一个词典对象,就将"{...}"字符串作为结果返回。
    if (i != 0) {
        return i > 0 ? PyString_FromString("{...}") : NULL;
    }

    // 如果词典中没有包含任何有效成员的话,
    // 就返回"{}"字符串对象,来表示当前词典是
    // 个空的词典。
    if (mp->ma_used == 0) {
        result = PyString_FromString("{}");
        goto Done;
    }

    // 创建一个临时的空列表,
    // 该列表是用于完成后面的转换工作的,
    // 例如:dict1 = {1:2, 3:4, 5:6},
    // 那么dict1在转为字符串对象时,
    // 会先将dict1中的每个成员都转为字符串,
    // 并将这些字符串都加入到临时列表中,
    // 这样就会得到一个['1: 2', '3: 4', '5: 6']
    // 的列表,接着会将列表的第一项加入'{',
    // 列表就变为:['{1: 2', '3: 4', '5: 6'],
    // 再将列表的最后一项加入'}',
    // 列表就会变为:['{1: 2', '3: 4', '5: 6}'],
    // 最后将列表里的这些字符串成员,通过
    // ', '连接在一起,以构成最终的
    // '{1: 2, 3: 4, 5: 6}'的字符串对象。
    pieces = PyList_New(0);
    if (pieces == NULL)
        goto Done;

    // 在将词典里的成员转为字符串时,
    // key与value之间会通过': '来隔开。
    // 例如:1:2对应的字符串为'1: 2',
    // 3:4对应的字符串为'3: 4'等等。
    colon = PyString_FromString(": ");
    if (colon == NULL)
        goto Done;

    /* Do repr() on each key+value pair, and insert ": " between them.
       Note that repr may mutate the dict. */
    i = 0;
    // 循环将词典里的每个成员都转为字符串,
    // 并将这些字符串依次添加到
    // 新建的临时列表中。
    // 例如:dict1 = {1:2, 3:4, 5:6},
    // 在经过下面的while循环后,
    // 就会得到一个['1: 2', '3: 4', '5: 6']的列表。
    while (PyDict_Next((PyObject *)mp, &i, &key, &value)) {
        int status;
        /* Prevent repr from deleting value during key format. */
        Py_INCREF(value);
        // 先将key转为字符串s
        s = PyObject_Repr(key);
        // 再在字符串s后面加入': '
        PyString_Concat(&s, colon);
        // 最后在字符串s的结尾加入value对应的字符串。
        // 这样成员1:2就可以被转为'1: 2',
        // 成员3:4就可以被转为'3: 4',以此类推。
        PyString_ConcatAndDel(&s, PyObject_Repr(value));
        Py_DECREF(value);
        if (s == NULL)
            goto Done;
        // 将'1: 2'之类的成员字符串
        // 添加到临时列表中。
        status = PyList_Append(pieces, s);
        Py_DECREF(s);  /* append created a new ref */
        if (status < 0)
            goto Done;
    }

    /* Add "{}" decorations to the first and last items. */
    assert(PyList_GET_SIZE(pieces) > 0);
    s = PyString_FromString("{");
    if (s == NULL)
        goto Done;
    temp = PyList_GET_ITEM(pieces, 0);
    PyString_ConcatAndDel(&s, temp);
    // 在临时列表的第一项中加入'{'
    // 例如:['1: 2', '3: 4', '5: 6']的临时列表,
    // 就会变为['{1: 2', '3: 4', '5: 6']
    PyList_SET_ITEM(pieces, 0, s);
    if (s == NULL)
        goto Done;

    s = PyString_FromString("}");
    if (s == NULL)
        goto Done;
    temp = PyList_GET_ITEM(pieces, PyList_GET_SIZE(pieces) - 1);
    PyString_ConcatAndDel(&temp, s);
    // 在临时列表的最后一项中加入'}'
    // 例如:['{1: 2', '3: 4', '5: 6']的临时列表,
    // 就会变为 ['{1: 2', '3: 4', '5: 6}']
    PyList_SET_ITEM(pieces, PyList_GET_SIZE(pieces) - 1, temp);
    if (temp == NULL)
        goto Done;

    /* Paste them all together with ", " between. */
    s = PyString_FromString(", ");
    if (s == NULL)
        goto Done;
    // 将临时列表里的每个字符串通过
    // ', '连接在一起,以构成最终的结果字符串。
    // 例如:['{1: 2', '3: 4', '5: 6}']的临时列表,
    // 在连接后,就可以得到:
    // '{1: 2, 3: 4, 5: 6}'的字符串结果了。
    result = _PyString_Join(s, pieces);
    Py_DECREF(s);

Done:
    Py_XDECREF(pieces);
    Py_XDECREF(colon);
    Py_ReprLeave((PyObject *)mp);
    return result;
}


    从上面的源码注释中可以看到,当词典的成员包含了自己在内时,在转换为字符串对象时,就会出现递归转换自己的情况。在这种情况下,为了防止出现无穷递归,Python会将出现递归转换的词典成员变为"{...}"的字符串:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {}
>>> dict1['self'] = dict1
>>> str(dict1)
"{'self': {...}}"
>>> 


type(variable):

    type脚本函数可以将参数对象的类型信息给显示出来,如下例所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {1:2, 3:4, 5:6}
[40769 refs]
>>> type(dict1)
....................................................

Breakpoint 1, type_repr (type=0x81c4fe0) at Objects/typeobject.c:685
685	    mod = type_module(type, NULL);
(gdb) c
Continuing.
<type 'dict'>
>>> 


    每个对象都有一个对应的类结构,对于词典对象而言,其类结构为PyDict_Type,该结构定义在Objects/dictobject.c文件中:

PyTypeObject PyDict_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dict",                   /*tp_name*/
    sizeof(PyDictObject),     /*tp_basicsize*/
    ................................................
};


    该结构的tp_name字段就记录了对应的类型名,type脚本函数在显示类型信息时,会将tp_name字段对应的字符串信息给显示出来。词典对象类型的tp_name"dict",因此,前面type(dict1)脚本执行后就显示出<type 'dict'>的信息了。

    有的对象类型的tp_name字段里还包含了模块名在内,例如Modules/_randommodule.c文件中定义的Random_Type类型:

static PyTypeObject Random_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_random.Random",                   /*tp_name*/
    sizeof(RandomObject),               /*tp_basicsize*/
    ................................................
};


    可以看到Random_Type这个类结构中的tp_name字段为"_random.Random",该字符串通过小数点将模块名和类型名分隔开,这里的_random为模块名,而Random则为类型名(Python中引入模块的概念,可以提高类型名的重用率,即不同的模块里可以使用相同的类型名)。type脚本函数在显示时会将模块名与类型名都显示出来:

[email protected]:~$ gdb -q python
....................................................
>>> import _random
>>> r = _random.Random()
>>> type(r)
<type '_random.Random'>
>>> 


    从前面的gdb调试中可以看到,type脚本函数最终会通过type_repr这个C函数去完成具体的操作,该函数定义在Objects/typeobject.c文件里:

static PyObject *
type_repr(PyTypeObject *type)
{
    PyObject *mod, *name, *rtn;
    char *kind;

    // 从tp_name字段中获取模块名,
    // 如果tp_name里没有包含模块名,
    // 则会返回"__builtin__"的内建模块名。
    // 例如词典对象类型的tp_name为"dict",
    // 该tp_name中没有小数点,也就不存在
    // 模块名,因此词典对象类型属于内建
    // 模块,内建模块名在此函数中会被
    // 省略掉。
    mod = type_module(type, NULL);
    if (mod == NULL)
        PyErr_Clear();
    else if (!PyString_Check(mod)) {
        Py_DECREF(mod);
        mod = NULL;
    }
    // 从tp_name字段中获取类型名。
    name = type_name(type, NULL);
    if (name == NULL) {
        Py_XDECREF(mod);
        return NULL;
    }

    // type脚本函数还可以将脚本通过class定义出来
    // 的类型所创建的对象的类型信息给显示出来,
    // 例如:
    // >>> import random
    // >>> r2 = random.Random()
    // >>> type(r2)
    // <class 'random.Random'>
    // >>> 
    // 上例里的random.Random是
    // 在/usr/local/lib/python2.7/random.py
    // 脚本文件里通过class定义过的类型。
    // 对于这种类型将使用class前缀,
    // 否则就使用type前缀。
    if (type->tp_flags & Py_TPFLAGS_HEAPTYPE)
        kind = "class";
    else
        kind = "type";

    // 将<前缀 '模块名.类型名'>给显示出来。
    // 如果是"__builtin__"的内建模块名的话,
    // 就可以省略模块名,而只显示
    // <前缀 '类型名'>的信息。
    if (mod != NULL && strcmp(PyString_AS_STRING(mod), "__builtin__")) {
        rtn = PyString_FromFormat("<%s '%s.%s'>",
                                  kind,
                                  PyString_AS_STRING(mod),
                                  PyString_AS_STRING(name));
    }
    else
        rtn = PyString_FromFormat("<%s '%s'>", kind, type->tp_name);

    Py_XDECREF(mod);
    Py_DECREF(name);
    return rtn;
}


    从上面的注释里可以看到,type脚本函数还可以将脚本中通过class定义过的类型信息给显示出来:

[email protected]:~$ gdb -q python
....................................................
>>> class mycls:
...   __metaclass__ = type
...   def test(self):
...     print 'test'
... 

Breakpoint 1, build_class (methods=0xb7ca27b4, bases=0xb7dcc034, 
    name=0xb7ca1ae0) at Python/ceval.c:4621
4621	    PyObject *metaclass = NULL, *result, *base;
....................................................
>>> m = mycls()
>>> type(m)
<class '__main__.mycls'>
>>> class mycls2:
...   def test2(self):
...     print 'test2'
... 

Breakpoint 1, build_class (methods=0xb7ca27b4, bases=0xb7dcc034, 
    name=0xb7ca1e98) at Python/ceval.c:4621
4621	    PyObject *metaclass = NULL, *result, *base;
....................................................
>>> m2 = mycls2()
>>> type(m2)
<type 'instance'>
>>> 


    可以看到,type(m)执行时显示出了我们所期望的<class '__main__.mycls'>信息,但是,type(m2)却显示出<type 'instance'>的信息,这是与Python构建class的过程相关的,Python内部会通过build_class函数来完成构建class的过程,该C函数定义在Python/ceval.c文件里:

static PyObject *
build_class(PyObject *methods, PyObject *bases, PyObject *name)
{
    PyObject *metaclass = NULL, *result, *base;

    if (PyDict_Check(methods))
        metaclass = PyDict_GetItemString(methods, "__metaclass__");
    if (metaclass != NULL)
        Py_INCREF(metaclass);
    else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
        base = PyTuple_GET_ITEM(bases, 0);
        metaclass = PyObject_GetAttrString(base, "__class__");
        if (metaclass == NULL) {
            PyErr_Clear();
            metaclass = (PyObject *)base->ob_type;
            Py_INCREF(metaclass);
        }
    }
    else {
        PyObject *g = PyEval_GetGlobals();
        if (g != NULL && PyDict_Check(g))
            metaclass = PyDict_GetItemString(g, "__metaclass__");
        if (metaclass == NULL)
            metaclass = (PyObject *) &PyClass_Type;
        Py_INCREF(metaclass);
    }
    result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods,
                                          NULL);
    ................................................
}


    当class中包含了__metaclass__属性,或者在全局变量中定义了__metaclass__变量时,或者class定义的类型所继承的base基类中包含__class__属性时,就会使用这些属性或变量所指向的类型来创建当前的class。前面例子中,将__metaclass__ 设置为type类型后,就会调用type_new函数来创建新的类型,type_new定义在Objects/typeobject.c文件中:

static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    ................................................
    obj = type->tp_new(type, args, kwds);
    ................................................
}

....................................................

static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
    ................................................
    /* Allocate the type object */
    type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);
    ................................................
    /* Initialize tp_flags */
    type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |
        Py_TPFLAGS_BASETYPE;
    ................................................
}

....................................................

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    ................................................
    (ternaryfunc)type_call,                     /* tp_call */
    ................................................
    type_new,                                   /* tp_new */
    ................................................
};


    上面的PyType_Typetype类型对应的C结构体,当使用type来创建类型时,会先进入tp_call字段所指向的type_call,再由type_call进入type_new,在type_new里,会通过metatype->tp_alloc在堆中为新建的类型(也就是我们在脚本中用class定义过的类型)分配额外的堆空间。同时,在新建类型的tp_flags字段中加入Py_TPFLAGS_HEAPTYPE标志,此标志表明Python为该类型分配了额外的堆空间。

    从前面介绍的type_repr函数的源码中可以看到,当某个类型的tp_flags字段里包含Py_TPFLAGS_HEAPTYPE标志时,type脚本函数在执行时,就会返回"<class ....>"的字符串信息了。

    从前面的build_class函数中,还可以看到,当没有定义__metaclass__属性,或者没有继承包含__class__属性的基类时,就会通过默认的PyClass_Type里的tp_new字段所指向的class_new函数来创建新类型,PyClass_Type定义在Objects/classobject.c文件中:

PyObject *
PyClass_New(PyObject *bases, PyObject *dict, PyObject *name)
     /* bases is NULL or tuple of classobjects! */
{
    ................................................

    op = PyObject_GC_New(PyClassObject, &PyClass_Type);
    ................................................
}

....................................................

static PyObject *
class_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    ................................................
    return PyClass_New(bases, dict, name);
}

....................................................

PyTypeObject PyClass_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "classobj",
    ................................................
    PyInstance_New,                             /* tp_call */
    ................................................
    class_new,                                  /* tp_new */
};

....................................................

/* Instance objects */

PyObject *
PyInstance_NewRaw(PyObject *klass, PyObject *dict)
{
    PyInstanceObject *inst;

    ................................................
    inst = PyObject_GC_New(PyInstanceObject, &PyInstance_Type);
    ................................................
    return (PyObject *)inst;
}

PyObject *
PyInstance_New(PyObject *klass, PyObject *arg, PyObject *kw)
{
    ................................................
    inst = (PyInstanceObject *) PyInstance_NewRaw(klass, NULL);
    ................................................
    return (PyObject *)inst;
}

PyTypeObject PyInstance_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "instance",
    ................................................
};


    可以看到,class_new会进入到PyClass_New函数,并在其中创建一个PyClass_Type类型的对象,因此,这种情况下通过class关键字新建的类型,其实是PyClass_Type类型的对象。在使用这种新建类型来创建对象时,最终会通过PyInstance_NewRaw函数来创建一个PyInstance_Type类型的对象,因此,将这种类型的对象传递给type脚本函数后,就会返回"<type 'instance'>"的信息了。

词典对象所支持的方法:

    词典对象所支持的方法,可以在mapp_methods数组中查看到,该数组定义在Objects/dictobject.c文件里:

static PyMethodDef mapp_methods[] = {
    {"__contains__",(PyCFunction)dict_contains,         METH_O | METH_COEXIST,
     contains__doc__},
    {"__getitem__", (PyCFunction)dict_subscript,        METH_O | METH_COEXIST,
     getitem__doc__},
    {"__sizeof__",      (PyCFunction)dict_sizeof,       METH_NOARGS,
     sizeof__doc__},
    {"has_key",         (PyCFunction)dict_has_key,      METH_O,
     has_key__doc__},
    {"get",         (PyCFunction)dict_get,          METH_VARARGS,
     get__doc__},
    {"setdefault",  (PyCFunction)dict_setdefault,   METH_VARARGS,
     setdefault_doc__},
    {"pop",         (PyCFunction)dict_pop,          METH_VARARGS,
     pop__doc__},
    {"popitem",         (PyCFunction)dict_popitem,      METH_NOARGS,
     popitem__doc__},
    {"keys",            (PyCFunction)dict_keys,         METH_NOARGS,
    keys__doc__},
    {"items",           (PyCFunction)dict_items,        METH_NOARGS,
     items__doc__},
    {"values",          (PyCFunction)dict_values,       METH_NOARGS,
     values__doc__},
    {"viewkeys",        (PyCFunction)dictkeys_new,      METH_NOARGS,
     viewkeys__doc__},
    {"viewitems",       (PyCFunction)dictitems_new,     METH_NOARGS,
     viewitems__doc__},
    {"viewvalues",      (PyCFunction)dictvalues_new,    METH_NOARGS,
     viewvalues__doc__},
    {"update",          (PyCFunction)dict_update,       METH_VARARGS | METH_KEYWORDS,
     update__doc__},
    {"fromkeys",        (PyCFunction)dict_fromkeys,     METH_VARARGS | METH_CLASS,
     fromkeys__doc__},
    {"clear",           (PyCFunction)dict_clear,        METH_NOARGS,
     clear__doc__},
    {"copy",            (PyCFunction)dict_copy,         METH_NOARGS,
     copy__doc__},
    {"iterkeys",        (PyCFunction)dict_iterkeys,     METH_NOARGS,
     iterkeys__doc__},
    {"itervalues",      (PyCFunction)dict_itervalues,   METH_NOARGS,
     itervalues__doc__},
    {"iteritems",       (PyCFunction)dict_iteritems,    METH_NOARGS,
     iteritems__doc__},
    {NULL,              NULL}   /* sentinel */
};


    可以看到,词典对象支持has_keygetsetdefaultpop之类的方法,在Python 3.x中已经不存在has_key的方法了,不过可以使用in关键字来实现has_key的功能。下面就对这些方法一一进行介绍。

词典对象的has_key方法:

    词典对象的has_key方法的使用,如下所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'end': 100, 'hel': 5, 'world': 2}
>>> dict1.has_key('end')
....................................................

Breakpoint 9, dict_has_key (mp=0xb7ca3034, key=0xb7dcc7d8)
    at Objects/dictobject.c:1928
1928	    if (PyErr_WarnPy3k("dict.has_key() not supported in 3.x; "
....................................................
(gdb) c
Continuing.
True
>>> dict1.has_key('eof')
False
>>> 


    has_key方法可以用于判断某个key是否存在于词典中,如果存在就返回True,否则返回False。

    该方法在内部会通过dict_has_key这个C函数去完成具体的操作,该函数定义在Objects/dictobject.c文件里:

static PyObject *
dict_contains(register PyDictObject *mp, PyObject *key)
{
    long hash;
    PyDictEntry *ep;

    // 先得到key的hash值。
    if (!PyString_CheckExact(key) ||
        (hash = ((PyStringObject *) key)->ob_shash) == -1) {
        hash = PyObject_Hash(key);
        if (hash == -1)
            return NULL;
    }
    // 通过词典的ma_lookup指向的函数,
    // 根据hash值来查询key在词典的ma_table
    // 数组中的成员位置。
    ep = (mp->ma_lookup)(mp, key, hash);
    if (ep == NULL)
        return NULL;
    // 如果找到的是dummy或empty成员
    // (它们的me_value为NULL),则说明
    // key不存在于词典中,
    // 结果就会返回False。
    // ------------------------------
    // 如果找到的是有效成员
    // (有效成员的me_value不为NULL),
    // 则说明key存在于词典中,
    // 结果就会返回True。
    return PyBool_FromLong(ep->me_value != NULL);
}

static PyObject *
dict_has_key(register PyDictObject *mp, PyObject *key)
{
    // 在Python 3.x中,词典已经不再支持has_key方法了,
    // 需要使用in关键字来代替。
    if (PyErr_WarnPy3k("dict.has_key() not supported in 3.x; "
                       "use the in operator", 1) < 0)
        return NULL;
    // 通过上面的dict_contains函数来判断
    // key是否存在于词典中。
    return dict_contains(mp, key);
}


    上面代码中涉及到的dummy,empty成员的概念,以及ma_lookup函数查询hash值的过程,请参考上一篇文章的内容。

    此外,在上面的dict_has_key函数里,还可以看到:Python 3.x中,词典对象已经不再支持has_key方法了,需要使用in关键字来代替:

[email protected]:~$ python3.2
Python 3.2.3 (default, Apr 10 2013, 05:29:11) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> dict1 = {'end':100, 'hello':3}
>>> dict1
{'end': 100, 'hello': 3}
>>> dict1.has_key('end')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'has_key'
>>> 'end' in dict1
True
>>> 'eof' in dict1
False
>>> 


    上面在Python 3.2.3的版本里,当词典对象使用has_key方法时,就会抛出 'dict' object has no attribute 'has_key' 的错误。可以使用in关键字来完成相同的功能。

词典对象的get方法:

    先来看个get方法的简单例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {1:2, 3:4, 5:6}
>>> dict1.get(1)
....................................................

Breakpoint 1, dict_get (mp=0xb7d44df4, args=0xb7d3868c)
    at Objects/dictobject.c:1938
1938	    PyObject *failobj = Py_None;
....................................................
(gdb) c
Continuing.
2
>>> 


    可以看到:get方法会调用的底层C函数为dict_get,该函数也定义在Objects/dictobject.c文件里:

static PyObject *
dict_get(register PyDictObject *mp, PyObject *args)
{
    PyObject *key;
    PyObject *failobj = Py_None;
    PyObject *val = NULL;
    long hash;
    PyDictEntry *ep;

    // 下面的PyArg_UnpackTuple函数的
    // 第三个参数为1,同时第四个参数为2,
    // 表示get方法最少要接受一个参数,
    // 同时最多只能接受两个参数。
    // 因此,get方法的第一个脚本参数是必须的,
    // 而该方法的第二个脚本参数则是可选的。
    // get会使用第一个参数作为key,
    // 如果key存在于词典中,
    // 则将key对应的value作为结果返回,
    // 如果key不存在于词典中,
    // 则将第二个参数作为结果返回。
    // 例如:
    // >>> dict1 = {'hello':100, 'world':200}
    // >>> dict1.get('hello')
    // 100
    // >>> dict1.get('zengl', 300)
    // 300
    // >>> print dict1.get('zengl')
    // None
    // >>>
    // 上例中,'hello'存在于dict1里,
    // 因此,dict1.get('hello')就将'hello'
    // 对应的值100作为结果返回。
    // 而'zengl'这个key不存在于dict1里,
    // 因此,dict1.get('zengl', 300)就会将
    // 第二个参数300作为结果返回。
    // 此外,由于下面的failobj的缺省值是Py_None,
    // 因此,如果没提供第二个参数的话,
    // 当key不存在于词典里时,会将None作为结果返回,
    // 上例中,dict1.get('zengl')执行后,
    // 就返回了None。
    if (!PyArg_UnpackTuple(args, "get", 1, 2, &key, &failobj))
        return NULL;

    // 先获取第一个参数key的hash值。
    if (!PyString_CheckExact(key) ||
        (hash = ((PyStringObject *) key)->ob_shash) == -1) {
        hash = PyObject_Hash(key);
        if (hash == -1)
            return NULL;
    }
    // 根据hash值,从词典里得到对应的成员。
    ep = (mp->ma_lookup)(mp, key, hash);
    if (ep == NULL)
        return NULL;
    val = ep->me_value;
    // 如果hash值得到的词典成员
    // 是dummy或empty成员的话,
    // 就说明第一个参数key不存在于词典里,
    // 那么就将第二个参数failobj作为结果返回。
    if (val == NULL)
        val = failobj;
    Py_INCREF(val);
    return val;
}


    从上面的代码中可以看到,get方法至少要有一个参数,同时最多只能接受两个参数。当第一个参数key不存在于词典里时,会将第二个参数作为结果返回(如果没提供第二个参数,就会将None对象作为结果返回):

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.get('hello')
100
>>> dict1.get('zengl', 300)
300
>>> print dict1.get('zengl')
None
>>> dict1['hello']
100
>>> dict1['zengl']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'zengl'
>>> 


    从上例中还可以看到,词典的get方法与使用中括号访问词典成员的方式相类似。只不过,当get方法找不到key对应的value时,会将第二个参数或者None作为结果返回,不会像中括号方式那样产生KeyError的错误。

词典对象的setdefault方法:

    下面是setdefault方法的使用例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.setdefault('zengl', 300)
....................................................

Breakpoint 1, dict_setdefault (mp=0xb7d44df4, args=0xb7d3a84c)
    at Objects/dictobject.c:1967
1967	    PyObject *failobj = Py_None;
....................................................
(gdb) c
Continuing.
300
>>> dict1
{'world': 200, 'hello': 100, 'zengl': 300}
>>> 


    从上例中可以看到,setdefault方法会调用的底层C函数为dict_setdefault,该函数同样定义在Objects/dictobject.c文件里:

static PyObject *
dict_setdefault(register PyDictObject *mp, PyObject *args)
{
    PyObject *key;
    PyObject *failobj = Py_None;
    PyObject *val = NULL;
    long hash;
    PyDictEntry *ep;

    // setdefault方法与get方法一样,
    // 最少要接受一个参数,
    // 同时最多只能接受两个参数。
    // 第一个参数是需要设置的key,
    // 如果key不存在于词典里,
    // 则将第二个参数failobj作为
    // key的value设置到词典里,
    // 如果key已经存在于词典里了,
    // 则跳过设置操作,保持key的原值不变,
    // 同时将key的原值作为结果返回。
    // 例如:
    // >>> dict1 = {'hello':100, 'world':200}
    // >>> dict1.setdefault('zengl', 300)
    // 300
    // >>> dict1['zengl']
    // 300
    // >>> dict1.setdefault('hello', 400)
    // 100
    // >>> dict1['hello']
    // 100
    // 本例中,一开始由于'zengl'不存在于dict1里,
    // 因此,dict1.setdefault('zengl', 300)执行后,
    // 就会将'zengl':300设置到dict1里。
    // 而'hello'这个key已经存在于词典中了,
    // 因此,dict1.setdefault('hello', 400)不会
    // 进行任何设置操作,'hello'对应的值依然会是
    // 原来的100。
    // 如果没提供第二个参数的话,
    // 由于failobj的缺省值是Py_None
    // 那么当第一个参数key不存在于词典里时,
    // 会将None作为值设置到词典里。
    // 例如:
    // >>> dict1 = {'hello':100, 'world':200}
    // >>> dict1.setdefault('zengl')
    // >>> print dict1['zengl']
    // None
    // >>>
    // 上面的dict1.setdefault('zengl')
    // 没提供第二个参数,就将None作为值
    // 设置到dict1里了。
    if (!PyArg_UnpackTuple(args, "setdefault", 1, 2, &key, &failobj))
        return NULL;

    // 先得到要设置的key的hash值。
    if (!PyString_CheckExact(key) ||
        (hash = ((PyStringObject *) key)->ob_shash) == -1) {
        hash = PyObject_Hash(key);
        if (hash == -1)
            return NULL;
    }
    // 通过ma_lookup根据hash值来查询
    // key对应的词典成员。
    ep = (mp->ma_lookup)(mp, key, hash);
    if (ep == NULL)
        return NULL;
    val = ep->me_value;
    // 如果找到的是dummy或empty成员
    // (这两类成员的me_value都为NULL),
    // 则说明key不存在于词典里,
    // 就将第二个参数作为key的value设置到词典里。
    // 如果找到的是有效成员(成员的me_value不为NULL)
    // 则跳过设置操作,保持key的原值不变,
    // 并将key的原值作为结果返回。
    if (val == NULL) {
        if (dict_set_item_by_hash_or_entry((PyObject*)mp, key, hash, ep,
                                           failobj) == 0)
            val = failobj;
    }
    Py_XINCREF(val);
    return val;
}


    从上面的代码里可以看到,只有在setdefault方法的第一个参数key不存在于词典中时,才会将第二个参数作为key的value设置到词典里。如果第一个参数key已经存在于词典里了,则不会进行任何设置操作。此外,如果没有提供第二个参数的话,那么当key不存在于词典里时,会将None对象作为value设置到词典里。以下是完整的例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.setdefault('zengl', 300)
300
>>> dict1
{'world': 200, 'hello': 100, 'zengl': 300}
>>> dict1.setdefault('hello', 400)
100
>>> dict1
{'world': 200, 'hello': 100, 'zengl': 300}
>>> dict1.setdefault('zl')
>>> dict1
{'zl': None, 'world': 200, 'hello': 100, 'zengl': 300}
>>> 


词典对象的pop方法:

    先来看个pop方法的简单例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.pop('hello')
....................................................

Breakpoint 2, dict_pop (mp=0xb7d44df4, args=0xb7d3868c)
    at Objects/dictobject.c:2008
2008	    PyObject *key, *deflt = NULL;
(gdb) c
Continuing.
100
>>> dict1
{'world': 200}
>>> 


    可以看到,pop方法会将第一个参数key对应的成员从词典里弹出去,该方法会调用的底层C函数为dict_pop,该函数也定义在Objects/dictobject.c文件里:

static PyObject *
dict_pop(PyDictObject *mp, PyObject *args)
{
    long hash;
    PyDictEntry *ep;
    PyObject *old_value, *old_key;
    PyObject *key, *deflt = NULL;

    // pop方法最多可以接受两个参数,
    // 其中第一个参数key是必须的,
    // 第二个参数deflt是可选的。
    // 此方法会在词典中搜索key,
    // 如果key存在于词典里,
    // 则将key对应的成员从词典里弹出去,
    // 也就是将key对应的value作为结果返回,
    // 同时将key所在的成员从词典里删除掉。
    // 例如:
    // >>> dict1 = {'hello':100, 'world':200}
    // >>> dict1.pop('hello')
    // 100
    // >>> dict1
    // {'world': 200}
    // >>> 
    // ------------------------------------
    // 如果key不存在于词典里,
    // 则将第二个参数作为结果返回,
    // 例如:
    // >>> dict1 = {'hello':100, 'world':200}
    // >>> dict1.pop('zengl', 'deflt')
    // 'deflt'
    // >>> 
    // 上例里,'zengl'不存在于dict1里,
    // 就将第二个参数'deflt'作为结果返回。
    // ------------------------------------
    // 如果没提供第二个参数的话,
    // 就会抛出KeyError的错误。
    // 例如:
    // >>> dict1 = {'hello':100, 'world':200}
    // >>> dict1.pop('zengl')
    // Traceback (most recent call last):
    //   File "<stdin>", line 1, in <module>
    // KeyError: 'zengl'
    // >>> 
    if(!PyArg_UnpackTuple(args, "pop", 1, 2, &key, &deflt))
        return NULL;
    // 如果是空词典(即词典里没有包含任何有效成员),
    // 则将第二个参数作为结果返回,
    // 如果没提供第二个参数,就会抛出KeyError的错误。
    // 例如:
    // >>> dict1 = {}
    // >>> dict1.pop('zengl', 1000)
    // 1000
    // >>> dict1.pop('zengl')
    // Traceback (most recent call last):
    //   File "<stdin>", line 1, in <module>
    // KeyError: 'zengl'
    // >>> 
    if (mp->ma_used == 0) {
        if (deflt) {
            Py_INCREF(deflt);
            return deflt;
        }
        set_key_error(key);
        return NULL;
    }
    // 先获取key的hash值。
    if (!PyString_CheckExact(key) ||
        (hash = ((PyStringObject *) key)->ob_shash) == -1) {
        hash = PyObject_Hash(key);
        if (hash == -1)
            return NULL;
    }
    // 根据hash值查询key对应的词典成员。
    ep = (mp->ma_lookup)(mp, key, hash);
    if (ep == NULL)
        return NULL;
    // 如果是dummy或empty成员(成员的me_value为NULL),
    // 那么如果提供了第二个参数的话,
    // 就将第二个参数作为结果返回,
    // 否则就抛出KeyError的错误。
    if (ep->me_value == NULL) {
        if (deflt) {
            Py_INCREF(deflt);
            return deflt;
        }
        set_key_error(key);
        return NULL;
    }
    old_key = ep->me_key;
    Py_INCREF(dummy);
    // 如果key存在于词典里,
    // 则将key对应的有效成员从词典里删除掉,
    // 也就是将该成员设置为dummy成员即可。
    ep->me_key = dummy;
    old_value = ep->me_value;
    ep->me_value = NULL;
    mp->ma_used--;
    Py_DECREF(old_key);
    // 最后将key在删除之前的原来的值作为结果返回。
    return old_value;
}


    从上面的代码及注释中可以看到,pop方法会将第一个参数key对应的词典成员从词典里删除掉,同时将删除之前的原来的值作为结果返回。如果key不存在于词典里,则将第二个参数作为结果返回,如果没提供第二个参数的话,就会抛出KeyError的错误。完整的例子如下:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.pop('hello')
100
>>> dict1
{'world': 200}
>>> dict1.pop('hello', 'deflt')
'deflt'
>>> dict1.pop('hello')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'hello'
>>> 


词典对象的popitem方法:

    以下是popitem方法的使用例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {1:2, 2:3, 3:4}
>>> dict1.popitem()

Breakpoint 4, dict_popitem (mp=0xb7ca2534) at Objects/dictobject.c:2050
2050	    Py_ssize_t i = 0;
....................................................
(1, 2)
>>> dict1.popitem()
(2, 3)
>>> dict1.popitem()
(3, 4)
>>> dict1.popitem()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'popitem(): dictionary is empty'
>>> 


    每执行一次popitem方法,就会从词典里弹出一个有效成员。当词典为空时(也就是词典里没有包含任何有效成员时),再执行popitem方法,就会抛出KeyError的错误。

    popitem方法会调用的底层C函数为dict_popitem,该函数定义在Objects/dictobject.c文件里:

static PyObject *
dict_popitem(PyDictObject *mp)
{
    Py_ssize_t i = 0;
    PyDictEntry *ep;
    PyObject *res;

    /* Allocate the result tuple before checking the size.  Believe it
     * or not, this allocation could trigger a garbage collection which
     * could empty the dict, so if we checked the size first and that
     * happened, the result would be an infinite loop (searching for an
     * entry that no longer exists).  Note that the usual popitem()
     * idiom is "while d: k, v = d.popitem()". so needing to throw the
     * tuple away if the dict *is* empty isn't a significant
     * inefficiency -- possible, but unlikely in practice.
     */
    // 此函数在弹出成员时,会以元组的形式返回结果,
    // 元组的第一项存储了弹出成员的key,
    // 元组的第二项则存储了弹出成员的value。
    res = PyTuple_New(2);
    if (res == NULL)
        return NULL;
    // 如果是空的词典
    // (即词典里没有包含任何有效成员时),
    // 就会抛出KeyError的错误。
    if (mp->ma_used == 0) {
        Py_DECREF(res);
        PyErr_SetString(PyExc_KeyError,
                        "popitem(): dictionary is empty");
        return NULL;
    }
    /* Set ep to "the first" dict entry with a value.  We abuse the hash
     * field of slot 0 to hold a search finger:
     * If slot 0 has a value, use slot 0.
     * Else slot 0 is being used to hold a search finger,
     * and we use its hash value as the first index to look.
     */
    // 如果词典的ma_table数组的第一项
    // 是一个有效成员的话,就直接将该成员弹出。
    // 如果第一项是dummy或empty成员的话,
    // 就以该成员的me_hash值作为起始索引,
    // 并从起始索引处开始搜索ma_table数组,
    // 当搜索到有效成员时,就将该成员弹出。
    // 在本函数的最后,会将下一次要进行搜索
    // 的起始索引值设置到ma_table数组的
    // 第一个成员的me_hash字段中。
    ep = &mp->ma_table[0];
    if (ep->me_value == NULL) {
        // 从第一个成员的me_hash字段里获取起始索引值
        i = ep->me_hash;
        /* The hash field may be a real hash value, or it may be a
         * legit search finger, or it may be a once-legit search
         * finger that's out of bounds now because it wrapped around
         * or the table shrunk -- simply make sure it's in bounds now.
         */
        // 如果起始索引值超出了ma_table数组的
        // 尺寸范围,则将其重置为1,
        // 索引0的成员在上面的if条件判断里
        // 已经被判定为dummy或empty成员了,
        // 因此,只需重置为索引1即可。
        if (i > mp->ma_mask || i < 1)
            i = 1;              /* skip slot 0 */
        // 通过while循环语句,从起始索引处开始,
        // 对ma_table数组进行搜索,
        // 当找到一个有效成员时
        // (有效成员的me_value不为NULL),就跳出循环。
        // 最后会将该成员作为结果弹出。
        while ((ep = &mp->ma_table[i])->me_value == NULL) {
            i++;
            // 当索引值超出了ma_table数组的尺寸范围时,
            // 会将索引值重置为1。
            // 然后从头开始继续查找。
            // 由于此函数的开头已经判断出词典不为空了,
            // 因此,本循环总会找到一个有效的成员,
            // 不会陷入无限死循环。
            if (i > mp->ma_mask)
                i = 1;
        }
    }
    // 将结果元组的第一项设置为弹出成员的key,
    // 同时将结果元组的第二项设置为弹出成员的value。
    PyTuple_SET_ITEM(res, 0, ep->me_key);
    PyTuple_SET_ITEM(res, 1, ep->me_value);
    Py_INCREF(dummy);
    // 弹出某个成员,就表示需要将该成员给删除掉,
    // 下面通过将弹出成员设置为dummy成员,
    // 从而将其给删除掉。
    ep->me_key = dummy;
    ep->me_value = NULL;
    // 删除弹出成员后,将词典的有效成员数减一。
    mp->ma_used--;
    assert(mp->ma_table[0].me_value == NULL);
    // 将下一次需要进行搜索的起始索引值
    // 设置到词典的ma_table数组的第一个成员的
    // me_hash字段里。
    mp->ma_table[0].me_hash = i + 1;  /* next place to start */
    // 将包含了弹出成员的key和value的元组作为结果返回。
    return res;
}


词典对象的keys方法:

    先来看个keys方法的简单例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.keys()
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) b *dict_keys
Breakpoint 6 at 0x80919cd: file Objects/dictobject.c, line 1222.
(gdb) c
Continuing.
....................................................

Breakpoint 6, dict_keys (mp=0xb7d44df4) at Objects/dictobject.c:1222
1222	{
(gdb) c
Continuing.
['world', 'hello']
>>> 


    keys方法会将词典里所包含的key,以列表的形式作为结果返回。该方法会调用的底层C函数为dict_keys。在gdb中对dict_keys函数下断点时,需要在dict_keys的前面加入星号(即使用b *dict_keys的命令),表示在该函数的入口处进行中断,因为dict_keys函数里有一个again标签,如果使用b dict_keys命令的话,则gdb会在错误的位置处下断点。

    dict_keys这个C函数也定义在Objects/dictobject.c文件里:

static PyObject *
dict_keys(register PyDictObject *mp)
{
    register PyObject *v;
    register Py_ssize_t i, j;
    PyDictEntry *ep;
    Py_ssize_t mask, n;

  again:
    // 先得到词典的有效成员数。
    n = mp->ma_used;
    // 根据词典的有效成员数创建一个新的列表,
    // 下面会将词典里所包含的key都添加
    // 到该列表中,最后会将此列表作为结果返回。
    v = PyList_New(n);
    if (v == NULL)
        return NULL;
    // 在某些很特殊的情况下,
    // 一般是在多线程环境下,
    // 词典的有效成员数可能会发生改变,
    // 这种情况下,只需跳转到开头,
    // 再重新获取一次词典的有效成员数,
    // 并重新创建一个列表即可。
    if (n != mp->ma_used) {
        /* Durnit.  The allocations caused the dict to resize.
         * Just start over, this shouldn't normally happen.
         */
        // 将之前创建的列表的引用计数减一。
        Py_DECREF(v);
        // 跳转到开头的again标签处。
        // 重新获取有效成员数,并重新创建一个列表。
        goto again;
    }
    ep = mp->ma_table;
    mask = mp->ma_mask;
    // 通过for循环,从索引值0开始,
    // 将词典的ma_table数组里的所有的
    // 有效成员的key对象,都添加到新建的列表中。
    for (i = 0, j = 0; i <= mask; i++) {
        // 有效成员的me_value不为NULL。
        if (ep[i].me_value != NULL) {
            PyObject *key = ep[i].me_key;
            Py_INCREF(key);
            PyList_SET_ITEM(v, j, key);
            j++;
        }
    }
    assert(j == n);
    // 将包含了词典key的列表作为结果返回。
    return v;
}


词典对象的items方法:

    先来看个items方法的简单例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.items()
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) b *dict_items
Breakpoint 1 at 0x8091c0d: file Objects/dictobject.c, line 1290.
(gdb) c
Continuing.


Breakpoint 1, dict_items (mp=0xb7d8ce44) at Objects/dictobject.c:1290
1290	{
(gdb) c
Continuing.
[('world', 200), ('hello', 100)]
>>> 


    items方法会将词典里所包含的成员,以列表的形式进行返回。结果列表中的每个元组都对应词典里的一个成员,如上面的('world', 200)就对应词典里的'world':200成员,而('hello', 100)就对应词典里的'hello':100成员。

    items方法会调用的底层C函数为dict_items,与之前介绍的dict_keys一样,在gdb里对此函数下断点时,需要在dict_items前面加入星号,表示在该函数的入口处下断点(因为此函数里也包含有again标签,不加星号的话,gdb会在错误的位置处下断点)。dict_items函数也定义在Objects/dictobject.c文件里:

static PyObject *
dict_items(register PyDictObject *mp)
{
    register PyObject *v;
    register Py_ssize_t i, j, n;
    Py_ssize_t mask;
    PyObject *item, *key, *value;
    PyDictEntry *ep;

    /* Preallocate the list of tuples, to avoid allocations during
     * the loop over the items, which could trigger GC, which
     * could resize the dict. :-(
     */
  again:
    // 先获取词典的有效成员数。
    n = mp->ma_used;
    // 根据有效成员数,创建一个新的列表,
    // 后面会将词典里的每个成员都以
    // 元组的形式添加到该列表中。
    v = PyList_New(n);
    if (v == NULL)
        return NULL;
    // 先为词典里的每个成员预先创建好对应
    // 的空元组,并将这些元组添加到上面新建的列表里,
    // 在本函数的最后,就只需将词典里的
    // 每个成员的key和value写入到这些元组中即可。
    for (i = 0; i < n; i++) {
        // 由于元组的第一项用于存储词典成员的key,
        // 而元组的第二项则用于存储词典成员的value,
        // 因此,新建的元组的尺寸为2。
        item = PyTuple_New(2);
        if (item == NULL) {
            Py_DECREF(v);
            return NULL;
        }
        // 将新建的元组添加到列表里。
        PyList_SET_ITEM(v, i, item);
    }
    // 在某些特殊情况下,
    // 如果词典的有效成员数发生了改变,
    // (例如多线程环境下,在其他线程里
    //  对词典的成员做了调整之类的)
    // 那么就跳转到开头的again标签处,
    // 去重新获取词典的有效成员数,
    // 并重新创建列表,以及重新创建列表里的元组。
    // 不过这种情况比较少见,
    // 一般情况下应该不会发生。
    if (n != mp->ma_used) {
        /* Durnit.  The allocations caused the dict to resize.
         * Just start over, this shouldn't normally happen.
         */
        Py_DECREF(v);
        goto again;
    }
    /* Nothing we do below makes any function calls. */
    ep = mp->ma_table;
    mask = mp->ma_mask;
    // 循环将词典里的每个成员的key和value,
    // 都写入到对应的元组里。
    for (i = 0, j = 0; i <= mask; i++) {
        // 只有me_value不为NULL的有效成员
        // 才会被写入元组。
        if ((value=ep[i].me_value) != NULL) {
            key = ep[i].me_key;
            // 先从结果列表中得到对应的元组。
            item = PyList_GET_ITEM(v, j);
            Py_INCREF(key);
            // 然后将词典成员的key设置为元组的第一项。
            PyTuple_SET_ITEM(item, 0, key);
            Py_INCREF(value);
            // 再将词典成员的value设置为元组的第二项。
            PyTuple_SET_ITEM(item, 1, value);
            j++;
        }
    }
    assert(j == n);
    // 将上面新建的列表作为结果返回。
    return v;
}


词典对象的values方法:

    下面是values方法的使用例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.values()
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) b *dict_values
Breakpoint 2 at 0x8091aed: file Objects/dictobject.c, line 1256.
(gdb) c
Continuing.


Breakpoint 2, dict_values (mp=0xb7d44df4) at Objects/dictobject.c:1256
1256	{
(gdb) c
Continuing.
[200, 100]
>>> 


    可以看到,此方法会将词典里所包含的value,以列表的形式作为结果返回。如上面的dict1里包含了100200这两个value,那么values方法执行后,就返回了[200, 100]的列表。

    values方法会调用的底层C函数是dict_values(与之前介绍的dict_keys相类似,在用gdb下断点时,需要在此函数的前面加入星号),该函数同样定义在Objects/dictobject.c文件里:

static PyObject *
dict_values(register PyDictObject *mp)
{
    register PyObject *v;
    register Py_ssize_t i, j;
    PyDictEntry *ep;
    Py_ssize_t mask, n;

  again:
    // 先得到词典的有效成员数。
    n = mp->ma_used;
    // 根据有效成员数创建一个新的列表,
    // 后面会将词典里所包含的有效成员的value
    // 都添加到此列表中。
    // 最后会将该列表作为结果返回。
    v = PyList_New(n);
    if (v == NULL)
        return NULL;
    // 在某些特殊情况下,
    // 如果词典的有效成员数发生了改变,
    // 则跳转到开头的again标签处,
    // 重新获取词典的有效成员数,
    // 并重新创建一个列表。
    if (n != mp->ma_used) {
        /* Durnit.  The allocations caused the dict to resize.
         * Just start over, this shouldn't normally happen.
         */
        Py_DECREF(v);
        goto again;
    }
    ep = mp->ma_table;
    mask = mp->ma_mask;
    // 将词典里包含的有效成员的value
    // 都添加到上面新建的列表中。
    for (i = 0, j = 0; i <= mask; i++) {
        // 有效成员的me_value不为NULL。
        if (ep[i].me_value != NULL) {
            PyObject *value = ep[i].me_value;
            Py_INCREF(value);
            PyList_SET_ITEM(v, j, value);
            j++;
        }
    }
    assert(j == n);
    // 将列表作为结果返回。
    return v;
}


词典对象的viewkeys,viewitems及viewvalues方法:

    这三个方法的使用,如下所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> viewkeys = dict1.viewkeys()
>>> viewkeys
dict_keys(['world', 'hello'])
>>> for i in viewkeys:
...   print i
... 
world
hello
>>> viewitems = dict1.viewitems()
>>> viewitems
dict_items([('world', 200), ('hello', 100)])
>>> for i in viewitems:
...   print i
... 
('world', 200)
('hello', 100)
>>> viewvalues = dict1.viewvalues()
>>> viewvalues
dict_values([200, 100])
>>> for i in viewvalues:
...   print i
... 
200
100
>>> 


    这三个方法与前面介绍的keys,items,values方法的区别在于,它们不会返回一个列表,而只是返回一个下面会介绍的dictviewobject对象,该对象里存储了词典对象的指针值,当由dictviewobject对象访问词典成员时,内部会通过迭代的方式来访问。因此,如果只需简单的查看和迭代词典里的成员的话,就可以使用这三个方法。

    与这三个方法相关的内部C结构和函数,都定义在Objects/dictobject.c文件里:

typedef struct {
    PyObject_HEAD
    PyDictObject *dv_dict;
} dictviewobject;

....................................................

static PyObject *
dictview_new(PyObject *dict, PyTypeObject *type)
{
    dictviewobject *dv;
    ................................................
    dv = PyObject_GC_New(dictviewobject, type);
    ................................................
    dv->dv_dict = (PyDictObject *)dict;
    ................................................
    return (PyObject *)dv;
}

PyTypeObject PyDictKeys_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dict_keys",                                /* tp_name */
    sizeof(dictviewobject),                     /* tp_basicsize */
    ................................................
    (getiterfunc)dictkeys_iter,                 /* tp_iter */
    ................................................
};

static PyObject *
dictkeys_new(PyObject *dict)
{
    return dictview_new(dict, &PyDictKeys_Type);
}

....................................................

PyTypeObject PyDictItems_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dict_items",                               /* tp_name */
    sizeof(dictviewobject),                     /* tp_basicsize */
    ................................................
    (getiterfunc)dictitems_iter,                /* tp_iter */
    ................................................
};

static PyObject *
dictitems_new(PyObject *dict)
{
    return dictview_new(dict, &PyDictItems_Type);
}

....................................................

PyTypeObject PyDictValues_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dict_values",                              /* tp_name */
    sizeof(dictviewobject),                     /* tp_basicsize */
    ................................................
    (getiterfunc)dictvalues_iter,               /* tp_iter */
    ................................................
};

static PyObject *
dictvalues_new(PyObject *dict)
{
    return dictview_new(dict, &PyDictValues_Type);
}


    上面的dictkeys_newdictitems_new以及dictvalues_new分别是viewkeysviewitemsviewvalues方法会调用的底层C函数。这三个C函数最终都会通过dictview_new函数去创建一个dictviewobject对象。

    dictviewobject对象的dv_dict字段中就存储了词典对象的指针值。

    viewkeys方法所创建的dictviewobject对象的ob_type字段,指向的是PyDictKeys_Type类型。当通过此方法所创建的对象来访问词典成员时,内部会先调用PyDictKeys_Type类型里的tp_iter字段所指向的dictkeys_iter函数来创建一个迭代器,再通过该迭代器将词典里的key给迭代出来。

    viewitems方法所创建的dictviewobject对象的ob_type字段,指向的是PyDictItems_Type类型。当通过此方法所创建的对象来访问词典成员时,内部会先调用PyDictItems_Type类型里的tp_iter字段所指向的dictitems_iter函数来创建一个迭代器,再通过该迭代器将词典里的key:value成员给迭代出来。

    viewvalues方法所创建的dictviewobject对象的ob_type字段,指向的是PyDictValues_Type类型。当通过此方法所创建的对象来访问词典成员时,内部会先调用PyDictValues_Type类型里的tp_iter字段所指向的dictvalues_iter函数来创建一个迭代器,再通过该迭代器将词典里的value给迭代出来。

    在对这三个方法所创建的dictviewobject对象进行显示输出时,都会先调用dictview_repr函数将其转为字符串对象,再进行显示(此函数也定义在Objects/dictobject.c文件里):

static PyObject *
dictview_repr(dictviewobject *dv)
{
    PyObject *seq;
    PyObject *seq_str;
    PyObject *result;

    // 先将dictviewobject对象转为列表对象,
    // 在转为列表对象时,内部会先新建一个列表,
    // 然后获取与dictviewobject对象相关联的
    // 词典对象的迭代器,再由迭代器将词典里的成员
    // 迭代出来,并添加到新建的列表里,
    // 根据迭代器的类型,
    // 添加到列表里的对象可能是词典成员的key,
    // 也有可能是(key, value)的元组,
    // 还有可能是词典成员的value。
    // 例如:
    // >>> dict1 = {1:2, 2:3, 3:4}
    // >>> vk = dict1.viewkeys()
    // >>> vk
    // dict_keys([1, 2, 3])
    // >>> 
    // viewkeys方法创建的dictviewobject对象
    // 在显示时会转为[1, 2, 3]的列表,
    // 该dictviewobject对象得到的
    // 词典迭代器会将词典里包含的key都迭代
    // 出来,并添加到新建的列表中。
    seq = PySequence_List((PyObject *)dv);
    if (seq == NULL)
        return NULL;

    // 将列表转为字符串对象。
    seq_str = PyObject_Repr(seq);
    if (seq_str == NULL) {
        Py_DECREF(seq);
        return NULL;
    }
    // 根据dictviewobject对象的类型名
    // 与列表字符串,得到结果字符串。
    // viewkeys方法所创建的对象,
    // 对应的类型为PyDictKeys_Type,
    // 该类型的tp_name(类型名)为"dict_keys",
    // 因此,viewkeys方法创建的对象所转为的字符串
    // 就是:dict_keys([...]),
    // 这里的[...]表示列表字符串。
    // viewitems方法创建的对象的类型名为"dict_items",
    // 此对象转为的字符串就是:dict_items([...])
    // viewvalues方法创建的对象的类型名为"dict_values",
    // 此对象转为的字符串就是:dict_values([...])
    result = PyString_FromFormat("%s(%s)", Py_TYPE(dv)->tp_name,
                                 PyString_AS_STRING(seq_str));
    Py_DECREF(seq_str);
    Py_DECREF(seq);
    // 将结果字符串返回。
    return result;
}


    有关词典迭代器的内容,下面会进行介绍。

词典对象的iterkeys,iteritems及itervalues方法:

    上面介绍的viewkeys之类的方法所创建的dictviewobject对象,在访问词典成员时,会间接的创建一个迭代器。如果想在脚本里直接创建一个词典迭代器的话,就可以使用iterkeysiteritems以及itervalues方法。这三个方法的使用,如下所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> iterkeys = dict1.iterkeys()
>>> for i in iterkeys:
...   print i
... 
world
hello
>>> iteritems = dict1.iteritems()
>>> for i in iteritems:
...   print i
... 
('world', 200)
('hello', 100)
>>> for i in dict1.itervalues():
...   print i
... 
200
100
>>> 


    从上例中可以看到:iterkeys方法所返回的迭代器,可以将词典里包含的成员的key给迭代出来。

    iteritems方法返回的迭代器,则可以将词典里包含的成员,以(key, value)元组的形式迭代出来。

    itervalues方法所返回的迭代器,可以将词典里包含的成员的value给迭代出来。

    这三个方法在执行时会调用的底层C函数如下(都定义在Objects/dictobject.c文件里):

static PyObject *
dict_iterkeys(PyDictObject *dict)
{
    return dictiter_new(dict, &PyDictIterKey_Type);
}

static PyObject *
dict_itervalues(PyDictObject *dict)
{
    return dictiter_new(dict, &PyDictIterValue_Type);
}

static PyObject *
dict_iteritems(PyDictObject *dict)
{
    return dictiter_new(dict, &PyDictIterItem_Type);
}

....................................................

/* Dictionary iterator types */

typedef struct {
    PyObject_HEAD
    PyDictObject *di_dict; /* Set to NULL when iterator is exhausted */
    Py_ssize_t di_used;
    Py_ssize_t di_pos;
    PyObject* di_result; /* reusable result tuple for iteritems */
    Py_ssize_t len;
} dictiterobject;

static PyObject *
dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
{
    dictiterobject *di;
    di = PyObject_GC_New(dictiterobject, itertype);
    if (di == NULL)
        return NULL;
    Py_INCREF(dict);
    di->di_dict = dict;
    di->di_used = dict->ma_used;
    di->di_pos = 0;
    di->len = dict->ma_used;
    if (itertype == &PyDictIterItem_Type) {
        di->di_result = PyTuple_Pack(2, Py_None, Py_None);
        if (di->di_result == NULL) {
            Py_DECREF(di);
            return NULL;
        }
    }
    else
        di->di_result = NULL;
    _PyObject_GC_TRACK(di);
    return (PyObject *)di;
}


    上面代码里的dict_iterkeysdict_itervalues以及dict_iteritems,分别是iterkeysitervalues以及iteritems方法会调用的底层C函数。这三个函数最终都会通过dictiter_new函数去创建词典迭代器。词典迭代器对应的内部C结构体为dictiterobject

/* Dictionary iterator types */

typedef struct {
    PyObject_HEAD
    PyDictObject *di_dict; /* Set to NULL when iterator is exhausted */
    Py_ssize_t di_used;
    Py_ssize_t di_pos;
    PyObject* di_result; /* reusable result tuple for iteritems */
    Py_ssize_t len;
} dictiterobject;


    该结构中,di_dict字段用于指向需要进行迭代的词典对象。

    di_used字段存储了词典里的有效成员数。

    di_pos字段存储了下一次迭代时的起始索引值,那么下一次迭代操作时,就会从该索引处开始对词典的ma_table数组里的有效成员进行搜索。

    di_result字段主要用于iteritems方法,对于此方法,Python会预先创建好一个元组,并让di_result字段指向该元组,这样迭代出来的key,value就可以直接写入到此元组里了。

    len字段用于记录迭代器的长度,该长度一开始等于词典的有效成员数。每迭代一次,len值就会减一。因此可以通过len字段的值来判断,词典里还有多少个成员没有被此迭代器迭代出来。

    iterkeys方法所创建的dictiterobject对象的ob_type字段,指向的是PyDictIterKey_Type类型。在对词典成员进行迭代操作时,内部会通过PyDictIterKey_Type类型里的tp_iternext字段所指向的dictiter_iternextkey函数,去进行迭代操作(该函数同样定义在Objects/dictobject.c文件里):

static PyObject *dictiter_iternextkey(dictiterobject *di)
{
    PyObject *key;
    register Py_ssize_t i, mask;
    register PyDictEntry *ep;
    PyDictObject *d = di->di_dict;

    ................................................

    // 得到本次迭代操作时,
    // 需要进行搜索的起始索引值。
    // 下面就会从该索引处开始,
    // 对词典的ma_table数组进行搜索,
    // 当搜索到有效成员时,就将该成员的key作为
    // 本次迭代的结果。
    i = di->di_pos;
    if (i < 0)
        goto fail;
    ep = d->ma_table;
    mask = d->ma_mask;
    // 从上面设置的起始索引处开始,
    // 对词典的ma_table数组进行搜索,
    // 当找到有效成员(me_value不为NULL)时,
    // 就跳出while循环。
    while (i <= mask && ep[i].me_value == NULL)
        i++;
    // 将当前索引值加一,并设置到
    // 迭代器的di_pos字段里,以作为
    // 下一次迭代的起始索引值。
    di->di_pos = i+1;
    // 如果搜索过程超出了数组的尺寸范围,
    // 则说明词典的有效成员都已经被迭代完了,
    // 就直接跳转到结尾的fail标签处。
    if (i > mask)
        goto fail;
    // 每执行一次迭代,就将迭代器的len
    // 字段的值减一。
    di->len--;
    // 将本次迭代所找到的有效成员的key
    // 作为结果返回。
    key = ep[i].me_key;
    Py_INCREF(key);
    return key;

fail:
    // 将迭代器的di_dict字段设置为NULL,
    // 同时返回NULL,以表示迭代结束。
    // Python就不会再继续进行迭代操作了。
    Py_DECREF(d);
    di->di_dict = NULL;
    return NULL;
}

PyTypeObject PyDictIterKey_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dictionary-keyiterator",                   /* tp_name */
    sizeof(dictiterobject),                     /* tp_basicsize */
    ................................................
    (iternextfunc)dictiter_iternextkey,         /* tp_iternext */
    ................................................
};


    itervalues方法所创建的dictiterobject对象的类型为PyDictIterValue_Type,该类型里的tp_iternext字段所指向的函数为dictiter_iternextvalue,迭代时就会调用此函数去进行迭代操作(该函数也定义在Objects/dictobject.c文件里):

static PyObject *dictiter_iternextvalue(dictiterobject *di)
{
    PyObject *value;
    register Py_ssize_t i, mask;
    register PyDictEntry *ep;
    PyDictObject *d = di->di_dict;

    ................................................
    // 得到本次迭代操作时,
    // 需要进行搜索的起始索引值。
    // 下面就会从该索引处开始,
    // 对词典的ma_table数组进行搜索,
    // 当搜索到有效成员时,就将该成员的value作为
    // 本次迭代的结果。
    i = di->di_pos;
    mask = d->ma_mask;
    if (i < 0 || i > mask)
        goto fail;
    ep = d->ma_table;
    // 从上面设置的起始索引处开始,
    // 对词典的ma_table数组进行搜索,
    // 当找到有效成员(me_value不为NULL)时,
    // 就跳出while循环。
    while ((value=ep[i].me_value) == NULL) {
        i++;
        if (i > mask)
            goto fail;
    }
    // 将当前索引值加一,并设置到
    // 迭代器的di_pos字段里,以作为
    // 下一次迭代的起始索引值。
    di->di_pos = i+1;
    // 每执行一次迭代,就将迭代器的len
    // 字段的值减一。
    di->len--;
    // 将本次迭代所找到的有效成员的value
    // 作为结果返回。
    Py_INCREF(value);
    return value;

fail:
    // 将迭代器的di_dict字段设置为NULL,
    // 同时返回NULL,以表示迭代结束。
    // Python就不会再继续进行迭代操作了。
    Py_DECREF(d);
    di->di_dict = NULL;
    return NULL;
}

PyTypeObject PyDictIterValue_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dictionary-valueiterator",                 /* tp_name */
    sizeof(dictiterobject),                     /* tp_basicsize */
    ................................................
    (iternextfunc)dictiter_iternextvalue,       /* tp_iternext */
    ................................................
};


    iteritems方法所创建的dictiterobject对象的类型为PyDictIterItem_Type,该类型里的tp_iternext字段所指向的函数为dictiter_iternextitem,迭代时就会调用此函数去进行迭代操作(该函数也定义在Objects/dictobject.c文件里):

static PyObject *dictiter_iternextitem(dictiterobject *di)
{
    PyObject *key, *value, *result = di->di_result;
    register Py_ssize_t i, mask;
    register PyDictEntry *ep;
    PyDictObject *d = di->di_dict;

    ................................................

    // 先得到本次迭代的起始索引值。
    i = di->di_pos;
    if (i < 0)
        goto fail;
    ep = d->ma_table;
    mask = d->ma_mask;
    // 从上面设置的起始索引处开始,
    // 对词典的ma_table数组进行搜索,
    // 当找到有效成员(me_value不为NULL)时,
    // 就跳出while循环。
    while (i <= mask && ep[i].me_value == NULL)
        i++;
    // 将当前索引值加一,并设置到
    // 迭代器的di_pos字段里,以作为
    // 下一次迭代的起始索引值。
    di->di_pos = i+1;
    if (i > mask)
        goto fail;

    // 只有当迭代器的di_result字段所对应的元组
    // 的引用计数为1时,才能将成员里的key,value
    // 设置到迭代器预先创建好的di_result元组中。
    // 否则就必须去创建一个新的元组。
    // 因为当di_result元组的引用计数不等于1时,
    // 就说明该元组除了被迭代器在使用外,
    // 还被脚本里的其他变量之类的给占用了,
    // 我们不可以对这样的元组进行设置。
    // 除非这些变量不再使用该元组了,
    // 我们才可以再对di_result元组进行设置。
    // 下面的result变量,在一开始就已经被设置为
    // di_result的对象指针了。
    if (result->ob_refcnt == 1) {
        Py_INCREF(result);
        Py_DECREF(PyTuple_GET_ITEM(result, 0));
        Py_DECREF(PyTuple_GET_ITEM(result, 1));
    } else {
        result = PyTuple_New(2);
        if (result == NULL)
            return NULL;
    }
    // 每执行一次迭代,就将迭代器的
    // len字段的值减一。
    di->len--;
    key = ep[i].me_key;
    value = ep[i].me_value;
    Py_INCREF(key);
    Py_INCREF(value);
    // 将本次迭代所找到的有效成员的key和value
    // 设置到元组里,元组的第一项对应为成员的
    // key,元组的第二项则对应为成员的value。
    PyTuple_SET_ITEM(result, 0, key);
    PyTuple_SET_ITEM(result, 1, value);
    // 将包含了成员的key和value的元组,
    // 作为迭代操作的结果返回。
    return result;

fail:
    // 返回NULL,来让Python停止迭代操作。
    Py_DECREF(d);
    di->di_dict = NULL;
    return NULL;
}

PyTypeObject PyDictIterItem_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dictionary-itemiterator",                  /* tp_name */
    sizeof(dictiterobject),                     /* tp_basicsize */
    ................................................
    (iternextfunc)dictiter_iternextitem,        /* tp_iternext */
    ................................................
};


    上面提到的dictiterobject的迭代器对象,还支持一个__length_hint__的私有方法,我们可以在dictiter_methods数组里看到这个方法,以及它会调用的底层C函数(这些C语言数组与C语言函数都定义在Objects/dictobject.c文件里):

static PyObject *
dictiter_len(dictiterobject *di)
{
    Py_ssize_t len = 0;
    if (di->di_dict != NULL && di->di_used == di->di_dict->ma_used)
        len = di->len;
    return PyInt_FromSize_t(len);
}

PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(it)).");

static PyMethodDef dictiter_methods[] = {
    {"__length_hint__", (PyCFunction)dictiter_len, METH_NOARGS, length_hint_doc},
    {NULL,              NULL}           /* sentinel */
};


    可以看到__length_hint__方法会调用的底层C函数为dictiter_len,该函数其实就是将迭代器里的len字段的值作为结果返回。下面是此方法使用的简单例子:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200, 'zl':300, 'll':400, 'oo':500}
>>> iteritems = dict1.iteritems()
>>> for i in iteritems:
...   print i
...   if iteritems.__length_hint__() == 3 :
...     break
... 
('zl', 300)
('world', 200)
>>> for i in iteritems:
...   print i
... 
('ll', 400)
('oo', 500)
('hello', 100)
>>> for i in iteritems:
...   print i
... 
>>> 


    如果只想显示词典里的前两个成员的话,就可以像上面的第一个for循环那样,当迭代器迭代出前两个成员后,词典中就还剩下3个成员没被迭代(迭代器的len字段的值为3),此时就可以通过break语句来跳出循环。

    第二个for语句再对迭代器进行迭代时,迭代器会将剩下的3个词典成员给迭代出来。当迭代器将所有的词典成员都迭代完后,再对其进行迭代时,就不会迭代出任何数据了,因而最后一个for语句执行后,没有产生任何输出。

    因此,我们可以借助迭代器的__length_hint__方法来迭代出前几个词典成员。此外,还可以看到,iteritems之类的方法所返回的迭代器,在迭代完所有的词典成员后,就不能再使用了,因为它不会再迭代出任何的数据。

词典对象的update方法:

    update方法的使用如下所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.update({'hello':300, 'zlzl':400})

Breakpoint 1, dict_update (self=0xb7ca2534, args=0xb7d3868c, kwds=0x0)
    at Objects/dictobject.c:1458
1458	    if (dict_update_common(self, args, kwds, "update") != -1)
(gdb) c
Continuing.
>>> dict1
{'world': 200, 'zlzl': 400, 'hello': 300}
>>> 


    update方法会对词典里的数据进行更新操作,对于参数中已经存在于词典里的key,会直接更新其value。而对于参数里不存在于词典里的key,则会执行添加操作。例如上面参数里的'hello'已经存在于dict1里了,因此,就会直接将dict1里的'hello'的value更新为300,而'zlzl'这个key不存在于dict1里,就会将'zlzl':400添加到dict1中。

    update方法会调用的底层C函数为dict_update,该函数也定义在Objects/dictobject.c文件里:

static int
dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, char *methname)
{
    PyObject *arg = NULL;
    int result = 0;

    // update方法最多只能接受一个参数,
    // 本函数中有一个args元组,还有一个kwds词典,
    // args元组里存储了传递给update方法的参数,
    // 而kwds词典里则存储了传递给update方法的
    // key = value的名值对数据。
    // 例如:
    // dict1.update({'zlzl':800}, hello = 900),
    // 其中的{'zlzl':800}属于参数,存储在args元组里,
    // 而hello = 900则属于key = value的名值对,
    // 存储在kwds词典里。hello = 900在kwds词典里
    // 会被存储为'hello':900的成员。
    // 此例中,update方法在执行时,
    // 既会将'zlzl':800更新或添加到dict1里,
    // 同时还会将'hello':900也更新或添加到dict1里。
    // 因此,update方法最多只能接受一个参数,
    // 只是针对args元组而言
    // (即args元组里最多只能包含一个参数对象)。
    // 对于kwds词典里包含的名值对则没有限制。
    if (!PyArg_UnpackTuple(args, methname, 0, 1, &arg))
        result = -1;

    // 传递给update方法的参数,
    // 既可以是词典之类的包含了'keys'方法的对象,
    // 还可以是列表之类的可迭代的对象。
    // 例如:
    // dict1.update({'hello':300, 'zlzl':400})
    // 里的{'hello':300, 'zlzl':400}就属于词典对象。
    // 而dict1.update([('hello', 300), ('zlzl', 400)])
    // 里的[('hello', 300), ('zlzl', 400)]则是列表对象。
    // 列表里的('hello', 300)会以'hello':300的成员形式
    // 更新或添加到dict1里,('zlzl', 400)则会以
    // 'zlzl':400的成员形式更新或添加到dict1里。
    // 如果参数是词典之类的包含了'keys'属性的对象,
    // 则会通过PyDict_Merge函数去完成更新或添加操作。
    // 如果参数是列表之类的可迭代的对象,
    // 则会通过PyDict_MergeFromSeq2去完成具体的
    // 更新或添加成员的操作。
    else if (arg != NULL) {
        if (PyObject_HasAttrString(arg, "keys"))
            result = PyDict_Merge(self, arg, 1);
        else
            result = PyDict_MergeFromSeq2(self, arg, 1);
    }
    // 将kwds里包含的名值对也更新到词典里。
    if (result == 0 && kwds != NULL)
        result = PyDict_Merge(self, kwds, 1);
    return result;
}

static PyObject *
dict_update(PyObject *self, PyObject *args, PyObject *kwds)
{
    // 调用上面的dict_update_common函数
    // 去完成具体的更新操作。
    if (dict_update_common(self, args, kwds, "update") != -1)
        Py_RETURN_NONE;
    return NULL;
}


    限于篇幅,上面代码里涉及到的PyDict_Merge与PyDict_MergeFromSeq2函数的代码并没有给出来,请读者自行通过gdb调试器来分析这两个函数的源代码。

    从上面的代码和注释里可以看到:update方法除了可以用词典或列表数据,来更新或添加成员外,还可以用名值对来更新或添加成员。如下所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello':100, 'world':200}
>>> dict1.update({'hello':300, 'zlzl':400})
>>> dict1
{'world': 200, 'zlzl': 400, 'hello': 300}
>>> dict1.update([('hello', 800), ('zlzl', 900)])
>>> dict1
{'world': 200, 'zlzl': 900, 'hello': 800}
>>> dict1.update(hello = 1000, lilo = 2000, zengl = 3000)
>>> dict1
{'zengl': 3000, 'world': 200, 'lilo': 2000, 'zlzl': 900, 'hello': 1000}
>>> 


词典对象的fromkeys方法:

    fromkeys方法的使用,如下所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict.fromkeys(['hello', 'world', 'zengl'], 'lilo')
{'world': 'lilo', 'hello': 'lilo', 'zengl': 'lilo'}
>>> dict1 = {}
>>> dict1.fromkeys(['hello', 'world', 'zengl'], 'lilo')
{'world': 'lilo', 'hello': 'lilo', 'zengl': 'lilo'}
>>> dict.fromkeys({'hello':1, 'world':2}, 101)
{'world': 101, 'hello': 101}
>>> dict1.fromkeys({'hello':1, 'world':2}, 101)
....................................................

Breakpoint 1, dict_fromkeys (cls=0x81c4fe0, args=0xb7d3a9d4)
    at Objects/dictobject.c:1343
1343	    PyObject *value = Py_None;
(gdb) c
Continuing.
{'world': 101, 'hello': 101}
>>> 


    上例中的dict表示词典类型,dict1则表示词典对象。无论是词典类型还是词典对象,在参数相同的情况下,fromkeys方法得到的结果都是一样的,说明该方法属于类方法。

    fromkeys方法会创建并返回一个新的词典对象,新词典里的key都是由第一个参数里的成员构成的,而这些key对应的value都会被设置为第二个参数的值。因此上面的dict.fromkeys(['hello', 'world', 'zengl'], 'lilo')得到的结果就会是{'world': 'lilo', 'hello': 'lilo', 'zengl': 'lilo'}。

    此方法的第一个参数可以是列表之类的可迭代的对象,还可以是词典对象。如果是词典对象,则只有词典里的key会被使用到,例如上面的dict.fromkeys({'hello':1, 'world':2}, 101)执行后,得到的结果为{'world': 101, 'hello': 101},参数词典中只有'hello','world'这些key被设置到了结果词典里,而结果词典里的value则都会被设置为第二个参数101。

    fromkeys方法会调用的底层C函数为dict_fromkeys,该函数也定义在Objects/dictobject.c文件里:

static PyObject *
dict_fromkeys(PyObject *cls, PyObject *args)
{
    PyObject *seq;
    PyObject *value = Py_None;
    PyObject *it;       /* iter(seq) */
    PyObject *key;
    PyObject *d;
    int status;

    // 先获取fromkeys方法的第一个参数seq与
    // 第二个参数value。
    if (!PyArg_UnpackTuple(args, "fromkeys", 1, 2, &seq, &value))
        return NULL;

    // 下面的cls表示调用fromkeys方法的对象的类型,
    // 如果cls是词典对象类型的话,那么下面
    // 的PyObject_CallObject函数执行后,
    // 就会创建出一个新的词典对象,
    // 该词典对象会作为最终的结果返回。
    // (如果没有特殊说明,下面都假设新建的
    // 是一个词典对象)
    d = PyObject_CallObject(cls, NULL);
    if (d == NULL)
        return NULL;

    if (PyDict_CheckExact(d) && ((PyDictObject *)d)->ma_used == 0) {
        // 如果第一个参数seq是词典对象的话,
        // 则将seq里的key提取出来,并与第二个
        // 参数value一起来构成新词典里的成员。
        // 例如:
        // dict.fromkeys({'hello':1, 'world':2}, 101)
        // 执行后,返回的结果就会是:
        // {'world': 101, 'hello': 101}
        if (PyDict_CheckExact(seq)) {
            PyDictObject *mp = (PyDictObject *)d;
            PyObject *oldvalue;
            Py_ssize_t pos = 0;
            PyObject *key;
            long hash;

            // 根据seq词典的大小来调整新词典的大小。
            if (dictresize(mp, Py_SIZE(seq))) {
                Py_DECREF(d);
                return NULL;
            }

            // 将seq里的key作为新词典中的key,
            // 而第二个参数value则作为新词典里的
            // key对应的值。
            while (_PyDict_Next(seq, &pos, &key, &oldvalue, &hash)) {
                Py_INCREF(key);
                Py_INCREF(value);
                if (insertdict(mp, key, hash, value)) {
                    Py_DECREF(d);
                    return NULL;
                }
            }
            return d;
        }
        // 如果第一个参数seq是Set类型的对象的话,
        // 则将seq里的key提取出来,并与第二个参数
        // value一起来构成新词典中的成员。
        // 例如:
        // dict.fromkeys({'hello','world'}, 100)
        // 执行后,得到的结果就会是:
        // {'world': 100, 'hello': 100},
        // 这里就会将{'hello', 'world'}这个set里的
        // 'hello'和'world'两个key给提取出来,
        // 以作为新词典里的key。
        // 并将第二个参数100作为新词典里的value。
        if (PyAnySet_CheckExact(seq)) {
            PyDictObject *mp = (PyDictObject *)d;
            Py_ssize_t pos = 0;
            PyObject *key;
            long hash;

            if (dictresize(mp, PySet_GET_SIZE(seq))) {
                Py_DECREF(d);
                return NULL;
            }

            while (_PySet_NextEntry(seq, &pos, &key, &hash)) {
                Py_INCREF(key);
                Py_INCREF(value);
                if (insertdict(mp, key, hash, value)) {
                    Py_DECREF(d);
                    return NULL;
                }
            }
            return d;
        }
    }

    // 如果第一个参数seq是列表之类的
    // 可迭代的对象的话,
    // 则先得到seq的迭代器。
    // 下面会通过迭代器将seq里包含的每个成员
    // 都迭代出来,以作为新词典里的key。
    it = PyObject_GetIter(seq);
    if (it == NULL){
        Py_DECREF(d);
        return NULL;
    }

    // 如果前面的cls类型所创建的对象
    // 是词典对象的话,则通过PyDict_SetItem函数来
    // 设置新词典里的成员。
    if (PyDict_CheckExact(d)) {
        while ((key = PyIter_Next(it)) != NULL) {
            status = PyDict_SetItem(d, key, value);
            Py_DECREF(key);
            if (status < 0)
                goto Fail;
        }
    // 如果cls所创建的对象是别的包含有
    // key:value名值对的对象时,
    // 则通过PyObject_SetItem函数去进行设置。
    // 这种对象类型不在本篇文章的讨论范围内。
    } else {
        while ((key = PyIter_Next(it)) != NULL) {
            status = PyObject_SetItem(d, key, value);
            Py_DECREF(key);
            if (status < 0)
                goto Fail;
        }
    }

    if (PyErr_Occurred())
        goto Fail;
    Py_DECREF(it);
    // 将前面通过cls类型新建的对象
    // (这里主要指的是词典对象)
    // 作为结果返回。
    return d;

Fail:
    Py_DECREF(it);
    Py_DECREF(d);
    // 执行失败则返回NULL。
    return NULL;
}


词典对象的clear方法:

    clear方法的使用,如下所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello': 100, 'world': 200}
>>> dict1.clear()
....................................................

Breakpoint 2, dict_clear (mp=0xb7ca3034) at Objects/dictobject.c:1998
1998	    PyDict_Clear((PyObject *)mp);
(gdb) c
Continuing.
>>> dict1
{}
>>> 


    clear方法可以将词典里的数据全部清空掉,清空以后,词典就会变为空词典。该方法会调用的底层C函数为dict_clear,该函数定义在Objects/dictobject.c文件里:

void
PyDict_Clear(PyObject *op)
{
    PyDictObject *mp;
    PyDictEntry *ep, *table;
    int table_is_malloced;
    Py_ssize_t fill;
    PyDictEntry small_copy[PyDict_MINSIZE];
#ifdef Py_DEBUG
    Py_ssize_t i, n;
#endif

    // 本函数的清空操作只能针对词典对象。
    // 如果不是词典对象,则直接返回。
    if (!PyDict_Check(op))
        return;
    mp = (PyDictObject *)op;
#ifdef Py_DEBUG
    n = mp->ma_mask + 1;
    i = 0;
#endif

    // 由于后面的EMPTY_TO_MINSIZE宏会将词典里的
    // ma_table字段的值重置为ma_smalltable数组的指针值。
    // 因此,先将词典的ma_table数组的指针值
    // 保存到table变量里,后面就可以通过
    // table将原数组里的成员的引用计数都减一,
    // 以达到清理数组成员的目的。
    table = mp->ma_table;
    assert(table != NULL);
    // 如果词典的ma_table所指向的数组不是
    // 词典的ma_smalltable数组时,
    // 就说明Python为ma_table分配了额外的
    // 堆空间,函数的最后就需要将分配的堆空间
    // 给释放掉。
    // 有关词典的ma_table数组与
    // ma_smalltable数组的作用,已经在
    // 上一篇文章里介绍过了。
    table_is_malloced = table != mp->ma_smalltable;

    /* This is delicate.  During the process of clearing the dict,
     * decrefs can cause the dict to mutate.  To avoid fatal confusion
     * (voice of experience), we have to make the dict empty before
     * clearing the slots, and never refer to anything via mp->xxx while
     * clearing.
     */
    fill = mp->ma_fill;
    // 如果词典的ma_table不等于ma_smalltable时,
    // 则可以直接通过EMPTY_TO_MINSIZE宏将词典里的
    // 各字段的值进行重置,比如将词典的ma_used
    // (有效成员数)重置为0等。
    // 如果ma_table等于ma_smalltable,
    // 则由于EMPTY_TO_MINSIZE宏还会将
    // ma_smalltable数组里的数据都重置为0,
    // 因此,在重置之前,需要先将ma_smalltable里的
    // 数据都拷贝到small_copy数组中。
    if (table_is_malloced)
        EMPTY_TO_MINSIZE(mp);

    else if (fill > 0) {
        /* It's a small table with something that needs to be cleared.
         * Afraid the only safe way is to copy the dict entries into
         * another small table first.
         */
        memcpy(small_copy, table, sizeof(small_copy));
        table = small_copy;
        EMPTY_TO_MINSIZE(mp);
    }
    /* else it's a small table that's already empty */

    /* Now we can finally clear things.  If C had refcounts, we could
     * assert that the refcount on table is 1 now, i.e. that this function
     * has unique access to it, so decref side-effects can't alter it.
     */
    // 下面的table在之前已经被设置为
    // 词典的ma_table数组的指针值了,
    // 因此,下面的for循环可以将
    // 原数组里的成员的key与value的
    // 引用计数都减一。
    // 这样就可以将成员里的key对象与
    // value对象给释放掉了。
    for (ep = table; fill > 0; ++ep) {
#ifdef Py_DEBUG
        assert(i < n);
        ++i;
#endif
        if (ep->me_key) {
            --fill;
            Py_DECREF(ep->me_key);
            Py_XDECREF(ep->me_value);
        }
#ifdef Py_DEBUG
        else
            assert(ep->me_value == NULL);
#endif
    }

    // 最后,如果词典的ma_table数组是
    // 在堆中分配的内存空间的话,
    // 还要将分配的堆空间给释放掉。
    if (table_is_malloced)
        PyMem_DEL(table);
}

....................................................

static PyObject *
dict_clear(register PyDictObject *mp)
{
    // 通过上面的PyDict_Clear函数去完成具体的
    // 清空操作。
    PyDict_Clear((PyObject *)mp);
    Py_RETURN_NONE;
}


词典对象的copy方法:

    copy方法的使用,如下所示:

[email protected]:~$ gdb -q python
....................................................
>>> dict1 = {'hello': 100, 'world': 200}
>>> dict2 = dict1.copy()
....................................................

Breakpoint 3, dict_copy (mp=0xb7ca3034) at Objects/dictobject.c:1649
1649	    return PyDict_Copy((PyObject*)mp);
(gdb) c
Continuing.
>>> dict1
{'world': 200, 'hello': 100}
>>> dict2
{'world': 200, 'hello': 100}
>>> 


    copy方法会创建一个新的词典,并将原词典里的所有成员都拷贝到新词典中。该方法会调用的底层C函数为dict_copy,该函数定义在Objects/dictobject.c文件里:

static PyObject *
dict_copy(register PyDictObject *mp)
{
    // 通过下面的PyDict_Copy函数去完成具体的操作。
    return PyDict_Copy((PyObject*)mp);
}

PyObject *
PyDict_Copy(PyObject *o)
{
    PyObject *copy;

    // 调用本函数的对象必须是一个词典对象。
    if (o == NULL || !PyDict_Check(o)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    // 先创建一个新的词典。
    copy = PyDict_New();
    if (copy == NULL)
        return NULL;
    // 然后将原词典里的所有成员
    // 都插入到新词典中。
    // 如果插入成功则将新词典返回。
    if (PyDict_Merge(copy, o, 1) == 0)
        return copy;
    Py_DECREF(copy);
    // 如果插入失败则返回NULL。
    return NULL;
}


    上面代码里涉及到的PyDict_Merge函数可以将原词典里的成员,都更新或添加到新词典中。该函数在前面提到过,不过限于篇幅,本篇文章没有给出PyDict_Merge函数的源代码,请读者通过gdb调试器来自行分析。

结束语:

    以上就是和词典相关的脚本函数及方法。

    从明天起,做一个幸福的人

    喂马、劈柴,周游世界

    从明天起,关心粮食和蔬菜

    我有一所房子,面朝大海,春暖花开

    从明天起,和每一个亲人通信

    告诉他们我的幸福

    那幸福的闪电告诉我的

    我将告诉每一个人

    给每一条河每一座山取一个温暖的名字

    陌生人,我也为你祝福

    愿你有一个灿烂的前程

    愿你有情人终成眷属

    愿你在尘世获得幸福

    我只愿面朝大海,春暖花开


——  海子
 
上下篇

下一篇: Python的time模块

上一篇: Python词典类型

相关文章

Python基本的操作运算符

Python的流程控制语句

Python的calendar模块

Python的基本语法

Python三角函数

Python词典类型