页面导航:
英文教程的下载地址:
本篇文章是根据英文教程《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_Type是
type类型对应的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_key,
get,
setdefault,
pop之类的方法,在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里包含了
100和
200这两个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_new,
dictitems_new以及
dictvalues_new分别是
viewkeys,
viewitems及
viewvalues方法会调用的底层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对象,在访问词典成员时,会间接的创建一个迭代器。如果想在脚本里直接创建一个词典迭代器的话,就可以使用
iterkeys,
iteritems以及
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_iterkeys,
dict_itervalues以及
dict_iteritems,分别是
iterkeys,
itervalues以及
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调试器来自行分析。
结束语:
以上就是和词典相关的脚本函数及方法。
从明天起,做一个幸福的人
喂马、劈柴,周游世界
从明天起,关心粮食和蔬菜
我有一所房子,面朝大海,春暖花开
从明天起,和每一个亲人通信
告诉他们我的幸福
那幸福的闪电告诉我的
我将告诉每一个人
给每一条河每一座山取一个温暖的名字
陌生人,我也为你祝福
愿你有一个灿烂的前程
愿你有情人终成眷属
愿你在尘世获得幸福
我只愿面朝大海,春暖花开
—— 海子