页面导航:
英文教程的下载地址:
本篇文章是根据英文教程《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网盘链接
元组类型:
Python的元组类型,在内部与列表类型具有非常相似的结构,可以参考下面这个例子:
[email protected]:~$ gdb -q python
....................................................
>>> tup1 = (1,2,3)
....................................................
Breakpoint 10, PyTuple_New (size=3) at Objects/tupleobject.c:53
53 if (size < 0) {
(gdb) backtrace
#0 PyTuple_New (size=3) at Objects/tupleobject.c:53
#1 0x0811aecf in tuple_of_constants (codestr=0xb7d8d4b0 "d", n=3,
consts=0xb7ca1a44) at Python/peephole.c:50
....................................................
#6 0x080fd5e9 in PyAST_Compile (mod=0x8271828, filename=0x81924ae "<stdin>",
flags=0xbffff2b8, arena=0x827fcd0) at Python/compile.c:292
#7 0x0812103a in run_mod (mod=0x8271828, filename=0x81924ae "<stdin>",
globals=0xb7d9d2b4, locals=0xb7d9d2b4, flags=0xbffff2b8, arena=0x827fcd0)
at Python/pythonrun.c:1374
....................................................
(gdb) c
Continuing.
Breakpoint 11, PyEval_EvalFrameEx (f=0xb7d43764, throwflag=0)
at Python/ceval.c:1137
1137 x = GETITEM(consts, oparg);
(gdb) n
1138 Py_INCREF(x);
(gdb)
1139 PUSH(x);
(gdb)
1140 goto fast_next_opcode;
(gdb) ptype *(PyTupleObject *)x
type = struct {
struct _object *_ob_next;
struct _object *_ob_prev;
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
Py_ssize_t ob_size;
PyObject *ob_item[1];
}
....................................................
>>> tup1
(1, 2, 3)
>>>
|
从上面的输出中可以看到,Python在用
PyAST_Compile编译脚本时,就已经通过
tuple_of_constants创建好(1,2,3)这个元组了,并将其作为常量加入到
consts常量表中。这样,当脚本运行时,就可以直接从
consts常量表里将该元组提取出来并赋值给tup1变量了。
此外,还可以看到,元组在内部所对应的C结构体为
PyTupleObject,该结构体的前4个字段是每个Python对象都具有的字段。
第5个
ob_size字段表示该元组中所包含的成员个数。
第6个
ob_item字段则是一个数组,其中包含了每个成员的对象指针。
之前我们介绍过的列表类型也具有非常相似的C结构,只不过列表的C结构中还多了一个allocated字段。另外,列表的ob_item数组是可动态调整的,也就是数组的大小及成员是可以被改变的。而元组的ob_item数组则是固定的,其大小及成员都是不可改变的。
创建元组时,还可以不加小括号,如下所示:
[email protected]:~$ gdb -q python
....................................................
>>> tup1 = 'physics', 'chemistry', 1997, 2000, 5
>>> tup1
('physics', 'chemistry', 1997, 2000, 5)
>>>
|
如果要创建只包含单个成员的元组时,该成员的后面需要加上逗号:
[email protected]:~$ gdb -q python
....................................................
>>> tup1 = (5)
>>> tup1
5
>>> type(tup1)
<type 'int'>
>>> tup1 = (5,)
>>> tup1
(5,)
>>> type(tup1)
<type 'tuple'>
>>> tup1 = 5,
>>> tup1
(5,)
>>> tup1 = ()
>>> tup1
()
|
可以看到,当没加逗号时,得到的将是括号里的对象类型,例如上面的
(5)得到的就会是整数对象5,而非元组。从上例中还可以看到,当使用空括号(即小括号里没有任何数据)时,将得到一个空元组。
从元组中获取单个成员:
下面看下从元组中获取单个成员的内部执行过程:
[email protected]:~$ gdb -q python
....................................................
>>> tup1 = (1,2,3,4,5)
>>> tup1
(1, 2, 3, 4, 5)
>>> tup1[4]
....................................................
Breakpoint 1, PyEval_EvalFrameEx (f=0xb7d8ce44, throwflag=0)
at Python/ceval.c:1388
1388 w = POP();
(gdb) l
....................................................
1386
1387 case BINARY_SUBSCR:
1388 w = POP();
1389 v = TOP();
1390 if (PyList_CheckExact(v) && PyInt_CheckExact(w)) {
1391 /* INLINE: list[int] */
1392 Py_ssize_t i = PyInt_AsSsize_t(w);
(gdb) l
1393 if (i < 0)
1394 i += PyList_GET_SIZE(v);
1395 if (i >= 0 && i < PyList_GET_SIZE(v)) {
1396 x = PyList_GET_ITEM(v, i);
1397 Py_INCREF(x);
1398 }
1399 else
1400 goto slow_get;
1401 }
1402 else
(gdb) l
1403 slow_get:
1404 x = PyObject_GetItem(v, w);
....................................................
(gdb) s
PyObject_GetItem (o=0xb7d97934, key=0x8207b20) at Objects/abstract.c:139
139 if (o == NULL || key == NULL)
....................................................
144 return m->mp_subscript(o, key);
(gdb) s
tuplesubscript (self=0xb7d97934, item=0x8207b20) at Objects/tupleobject.c:704
704 if (PyIndex_Check(item)) {
....................................................
710 return tupleitem(self, i);
(gdb) s
tupleitem (a=0xb7d97934, i=4) at Objects/tupleobject.c:385
385 if (i < 0 || i >= Py_SIZE(a)) {
....................................................
5
>>>
|
从上面的输出中可以看到,当从元组里获取单个成员时,会先进入到
PyEval_EvalFrameEx函数,再由
BINARY_SUBSCR这个opcode操作码的执行过程去完成获取成员的操作。该执行过程里,会先进入
PyObject_GetItem,再进入
tuplesubscript,最终由
tupleitem来完成元组的获取单个成员的具体操作。
tuplesubscript与
tupleitem都是定义在
Objects/tupleobject.c文件中的C函数:
static PyObject *
tupleitem(register PyTupleObject *a, register Py_ssize_t i)
{
/*
当索引值超出范围时,
会抛出IndexError的错误。
*/
if (i < 0 || i >= Py_SIZE(a)) {
PyErr_SetString(PyExc_IndexError, "tuple index out of range");
return NULL;
}
Py_INCREF(a->ob_item[i]);
/*
将ob_item数组中索引值为i
的成员的对象指针,作为结果返回。
*/
return a->ob_item[i];
}
....................................................
static PyObject*
tuplesubscript(PyTupleObject* self, PyObject* item)
{
if (PyIndex_Check(item)) {
Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (i == -1 && PyErr_Occurred())
return NULL;
/*
如果索引值i小于0,则将其加上元组的尺寸,
从而尽可能的将其转为有效的索引值。
*/
if (i < 0)
i += PyTuple_GET_SIZE(self);
// 通过tupleitem来获取到索引i对应的成员。
return tupleitem(self, i);
}
................................................
}
|
从上面的代码中可以看到:当获取成员时,如果索引值小于0,则会将其加上元组的尺寸,从而尽可能的将其转为一个有效的索引值。之所以是尽可能,是因为如果索引值太小,加上元组尺寸后还是小于0的话,那么该索引值依然会是无效的。当索引值无效或者超出了元组的尺寸大小时,则会抛出
IndexError的错误。如下所示:
[email protected]:~$ gdb -q python
....................................................
>>> tup1
(1, 2, 3, 4, 5)
>>> tup1[-1]
5
>>> tup1[-2]
4
>>> tup1[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
>>> tup1[-10]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
>>>
|
从元组中获取slice片段:
先来看个简单的例子:
[email protected]:~$ gdb -q python
....................................................
>>> tup1 = (1,2,3,4,5)
>>> tup1[1:5]
....................................................
Breakpoint 1, tupleslice (a=0xb7d97934, ilow=1, ihigh=5)
at Objects/tupleobject.c:401
401 if (ilow < 0)
....................................................
(2, 3, 4, 5)
>>> tup1[1:]
Breakpoint 1, tupleslice (a=0xb7d97934, ilow=1, ihigh=2147483647)
at Objects/tupleobject.c:401
401 if (ilow < 0)
....................................................
(2, 3, 4, 5)
>>>
|
当需要获取元组中的片段时,内部会通过
tupleslice这个C函数去完成具体的操作。该函数的第一个参数a表示需要操作的元组,第二个参数ilow表示片段的起始索引,第三个参数ihigh则表示片段的结束索引(片段不包括ihigh在内)。例如当ilow为
1,ihigh为
5时,就表示获取索引
1到索引
5(不包括
5在内)的元组片段。该C函数定义在
Objects/tupleobject.c文件中:
static PyObject *
tupleslice(register PyTupleObject *a, register Py_ssize_t ilow,
register Py_ssize_t ihigh)
{
register PyTupleObject *np;
PyObject **src, **dest;
register Py_ssize_t i;
Py_ssize_t len;
// 当ilow小于0时,就将其重置为0
if (ilow < 0)
ilow = 0;
/*
如果ihigh超出了元组a的尺寸范围,
则将ihigh设置为元组的长度值。
例如,tup1[1:]脚本在执行时,
ilow为1,ihigh在没提供时,默认会
是一个很大的整数值,如上例中,
ihigh就为2147483647,在下面的if语句
里会将其重置为元组的长度值。
如果tup1的长度为5,则ihigh就会被重置为5。
*/
if (ihigh > Py_SIZE(a))
ihigh = Py_SIZE(a);
/*
如果ihigh小于ilow,则将ihigh重置为ilow的值。
在这种情况下,最后将会返回一个空元组。
例如:tup1[1:0]执行后,将得到一个空元组。
*/
if (ihigh < ilow)
ihigh = ilow;
/*
当ilow为0,ihigh为元组的长度时,
相当于将整个元组返回,这时就只需
增加当前元组的引用计数,再直接将
当前元组的对象指针作为结果返回即可。
这种情况下,获取到的片段就是当前元组。
*/
if (ilow == 0 && ihigh == Py_SIZE(a) && PyTuple_CheckExact(a)) {
Py_INCREF(a);
return (PyObject *)a;
}
/*
计算出需要获取的片段的长度值,
该长度是不包括ihigh在内的。
例如:当ilow为1,ihigh为5时,
片段的长度就是5 - 1 = 4,也就是
获取索引1、2、3、4这4个成员所构成的片段,
该片段不包括索引5在内。
*/
len = ihigh - ilow;
// 根据片段长度创建一个新的元组。
np = (PyTupleObject *)PyTuple_New(len);
if (np == NULL)
return NULL;
src = a->ob_item + ilow;
dest = np->ob_item;
/*
将当前元组中的ilow到ihigh(不包括ihigh在内)
的成员,拷贝到新创建的元组中,
并将新元组作为结果返回。
此时,获取到的片段将会是一个新元组。
*/
for (i = 0; i < len; i++) {
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
return (PyObject *)np;
}
|
元组的相关操作:
元组与列表一样,都是可以包含多个成员的集合。元组在作为集合时,其与集合相关的操作都定义在
tuple_as_sequence变量中,该变量也定义在
Objects/tupleobject.c文件里:
static PySequenceMethods tuple_as_sequence = {
(lenfunc)tuplelength, /* sq_length */
(binaryfunc)tupleconcat, /* sq_concat */
(ssizeargfunc)tuplerepeat, /* sq_repeat */
(ssizeargfunc)tupleitem, /* sq_item */
(ssizessizeargfunc)tupleslice, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
(objobjproc)tuplecontains, /* sq_contains */
};
|
上面结构中的
tupleitem与
tupleslice函数,在前面已经介绍过了。下面再对其他几个C函数进行介绍。
len(tuple):
当使用
len内建函数来获取元组的长度(也就是其所包含的成员数)时,就会通过
tuplelength函数来完成具体的操作(该函数也定义在
Objects/tupleobject.c文件里):
[email protected]:~$ gdb -q python
....................................................
>>> tup1 = (1,2,3,4,5)
>>> len(tup1)
....................................................
Breakpoint 2, tuplelength (a=0xb7d0fc4c) at Objects/tupleobject.c:367
367 return Py_SIZE(a);
(gdb) c
Continuing.
5
>>>
|
tuplelength函数中,其实就是通过
Py_SIZE宏来获取元组的长度信息的,该宏定义在Include/object.h头文件里:
#define Py_REFCNT(ob) (((PyObject*)(ob))->ob_refcnt)
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size)
|
Py_SIZE宏最后会通过元组对象里的
ob_size字段来获取到长度信息。
元组的加法运算:
当对元组进行加法运算时,内部会通过上面
tuple_as_sequence变量中的
tupleconcat函数去完成具体的操作,如下例所示:
[email protected]:~$ gdb -q python
....................................................
>>> tup1 = (12, 34.56)
>>> tup2 = ('abc', 'xyz')
>>> tup3 = tup1 + tup2
....................................................
Breakpoint 3, tupleconcat (a=0xb7d3a92c, bb=0xb7d3a99c)
at Objects/tupleobject.c:442
442 if (!PyTuple_Check(bb)) {
....................................................
>>> tup3
(12, 34.56, 'abc', 'xyz')
>>>
|
tupleconcat函数定义在
Objects/tupleobject.c文件里:
static PyObject *
tupleconcat(register PyTupleObject *a, register PyObject *bb)
{
register Py_ssize_t size;
register Py_ssize_t i;
PyObject **src, **dest;
PyTupleObject *np;
/*
元组只能与元组进行连接操作。
如果与其他类型的对象进行连接操作的话,
就会抛出TypeError错误。
*/
if (!PyTuple_Check(bb)) {
PyErr_Format(PyExc_TypeError,
"can only concatenate tuple (not \"%.200s\") to tuple",
Py_TYPE(bb)->tp_name);
return NULL;
}
#define b ((PyTupleObject *)bb)
// 得到两个元组连接后的总长度。
size = Py_SIZE(a) + Py_SIZE(b);
if (size < 0)
return PyErr_NoMemory();
// 根据总长度创建一个新的空元组。
np = (PyTupleObject *) PyTuple_New(size);
if (np == NULL) {
return NULL;
}
src = a->ob_item;
dest = np->ob_item;
/*
先将第一个元组里的成员拷贝
到新元组的开头。
*/
for (i = 0; i < Py_SIZE(a); i++) {
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
src = b->ob_item;
dest = np->ob_item + Py_SIZE(a);
/*
再将第二个元组里的成员拷贝
到新元组的结尾。
从而将两个元组连接成为一个新的元组。
*/
for (i = 0; i < Py_SIZE(b); i++) {
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
// 将新元组作为结果返回。
return (PyObject *)np;
#undef b
}
|
可以看到,元组的加运算其实就是将两个元组里的成员进行连接操作,从而构建出一个新元组。此外,从上面代码中还可以看到,元组只能与元组进行加运算,如果与其他类型的对象进行加运算的话,则会抛出
TypeError的错误,如下例所示:
[email protected]:~$ gdb -q python
....................................................
>>> tup3 = (12, 34.56) + 'abc'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "str") to tuple
>>> tup3 = (12, 34.56) + ['abc', 'xyz', 'b']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple
>>>
|
元组的乘法运算:
当对元组进行乘法运算时,内部会通过上面
tuple_as_sequence变量中的
tuplerepeat函数去完成具体的操作,如下例所示:
[email protected]:~$ gdb -q python
....................................................
>>> tup1 = (12, 34.56)
>>> tup1
(12, 34.56)
>>> tup1 * 4
....................................................
Breakpoint 5, tuplerepeat (a=0xb7d3a8f4, n=4) at Objects/tupleobject.c:481
481 if (n < 0)
(gdb) p *(PyIntObject *)a->ob_item[0]
$13 = {_ob_next = 0x8207a94, _ob_prev = 0x8207a6c, ob_refcnt = 17,
ob_type = 0x81c2d80, ob_ival = 12}
(gdb) c
Continuing.
(12, 34.56, 12, 34.56, 12, 34.56, 12, 34.56)
>>>
|
tuplerepeat函数也定义在
Objects/tupleobject.c文件中:
static PyObject *
tuplerepeat(PyTupleObject *a, Py_ssize_t n)
{
Py_ssize_t i, j;
Py_ssize_t size;
PyTupleObject *np;
PyObject **p, **items;
/*
这里的a表示当前需要操作的元组,
n表示需要重复的次数。
如果n小于0,则将其重置为0
*/
if (n < 0)
n = 0;
/*
如果当前元组是空元组(即长度为0)时,
或者当需要重复的次数为1时,
就直接将当前元组作为结果返回。
*/
if (Py_SIZE(a) == 0 || n == 1) {
if (PyTuple_CheckExact(a)) {
/* Since tuples are immutable, we can return a shared
copy in this case */
Py_INCREF(a);
return (PyObject *)a;
}
if (Py_SIZE(a) == 0)
return PyTuple_New(0);
}
/*
根据当前元组的长度乘以重复次数n,
得到重复后新元组的总长度。
*/
size = Py_SIZE(a) * n;
if (size/Py_SIZE(a) != n)
return PyErr_NoMemory();
// 根据总长度创建一个新的元组。
np = (PyTupleObject *) PyTuple_New(size);
if (np == NULL)
return NULL;
p = np->ob_item;
items = a->ob_item;
/*
通过双循环,将当前元组里的所有成员
重复拷贝n次到新元组中。
例如,当a为(12, 34.56),n为4时,
重复拷贝后,得到的新元组就会是:
(12, 34.56, 12, 34.56, 12, 34.56, 12, 34.56)
*/
for (i = 0; i < n; i++) {
for (j = 0; j < Py_SIZE(a); j++) {
*p = items[j];
Py_INCREF(*p);
p++;
}
}
// 将新元组作为结果返回。
return (PyObject *) np;
}
|
因此,元组的乘法运算的结果,其实就是将元组中的所有成员经过重复拷贝后得到的结果。
元组的包含操作:
当通过in关键字,来判断元组中是否包含某个对象时。Python内部会通过前面提到的
tuple_as_sequence变量中的
tuplecontains函数,去完成具体的操作。如下例所示:
[email protected]:~$ gdb -q python
....................................................
>>> 3 in (1, 2, 3)
....................................................
Breakpoint 6, tuplecontains (a=0xb7c9eab4, el=0x8207b34)
at Objects/tupleobject.c:376
376 for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
....................................................
True
>>> 4 in (1, 2, 3)
False
>>>
|
tuplecontains函数定义在
Objects/tupleobject.c文件中:
static int
tuplecontains(PyTupleObject *a, PyObject *el)
{
Py_ssize_t i;
int cmp;
for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
cmp = PyObject_RichCompareBool(el, PyTuple_GET_ITEM(a, i),
Py_EQ);
return cmp;
}
|
在前面 3 in (1, 2, 3) 的例子中,上面函数里的a就对应(1, 2, 3)这个元组,而el就对应整数3。
tuplecontains函数会通过
for循环,将a里的每个成员都与el进行比较,如果找到与el值相等的成员时,就说明元组a中包含了el(结果就会返回True)。如果找不到值相等的成员,则说明元组中不包含el(结果就会返回False)。
元组的迭代操作:
下面看下与元组的迭代操作相关的例子:
[email protected]:~$ gdb -q python
....................................................
>>> for x in ('hello', 'world', 'zengl'):
... print x
...
Breakpoint 8, tuple_iter (seq=0xb7c9eab4) at Objects/tupleobject.c:1040
1040 if (!PyTuple_Check(seq)) {
(gdb) c
Continuing.
Breakpoint 9, tupleiter_next (it=0xb7ca1c04) at Objects/tupleobject.c:968
968 assert(it != NULL);
(gdb) c
Continuing.
hello
Breakpoint 9, tupleiter_next (it=0xb7ca1c04) at Objects/tupleobject.c:968
968 assert(it != NULL);
(gdb) c
Continuing.
world
Breakpoint 9, tupleiter_next (it=0xb7ca1c04) at Objects/tupleobject.c:968
968 assert(it != NULL);
(gdb) c
Continuing.
zengl
Breakpoint 9, tupleiter_next (it=0xb7ca1c04) at Objects/tupleobject.c:968
968 assert(it != NULL);
(gdb) c
Continuing.
>>>
|
从输出中可以看到,在脚本里用
for语句对元组进行迭代时,内部会先调用
tuple_iter这个C函数来获取元组相关的迭代器。再根据该迭代器,通过
tupleiter_next函数将元组里的每个成员依次获取出来。这两个C函数以及与元组迭代器相关的C结构体,都定义在
Objects/tupleobject.c文件里:
/*********************** Tuple Iterator **************************/
// 以下是元组迭代器相关的C结构体的定义。
typedef struct {
PyObject_HEAD
/*
it_index字段中存储了
迭代所需的元组索引值
*/
long it_index;
/*
it_seq字段则存储了
需要进行迭代的元组的对象指针,
当元组中的所有成员
都迭代完后,该字段的值
会被设置为NULL(即0)
*/
PyTupleObject *it_seq; /* Set to NULL when iterator is exhausted */
} tupleiterobject;
....................................................
// tupleiter_next函数会根据元组迭代器,
// 将元组中的成员给获取出来。
static PyObject *
tupleiter_next(tupleiterobject *it)
{
PyTupleObject *seq;
PyObject *item;
assert(it != NULL);
/*
从迭代器中获取
需要进行迭代的元组对象。
*/
seq = it->it_seq;
if (seq == NULL)
return NULL;
assert(PyTuple_Check(seq));
/*
如果迭代器的it_index索引值
小于元组的总成员数时,说明当前的
it_index是一个有效的索引值,
则通过PyTuple_GET_ITEM宏将元组中的
it_index索引对应的成员给获取出来,
并将该成员对象作为本次迭代的结果。
*/
if (it->it_index < PyTuple_GET_SIZE(seq)) {
item = PyTuple_GET_ITEM(seq, it->it_index);
++it->it_index;
Py_INCREF(item);
return item;
}
Py_DECREF(seq);
/*
如果元组中的所有成员都已经迭代完了,
则将迭代器的it_seq字段设置为NULL,
并将NULL作为结果返回。当该函数返回
NULL时,Python就会停止迭代操作。
*/
it->it_seq = NULL;
return NULL;
}
....................................................
// tuple_iter函数会创建一个与元组相关的迭代器。
static PyObject *
tuple_iter(PyObject *seq)
{
/*
这里的tupleiterobject就是上面定义过的
与元组迭代器相关的C结构体。
*/
tupleiterobject *it;
if (!PyTuple_Check(seq)) {
PyErr_BadInternalCall();
return NULL;
}
// 创建一个新的元组迭代器。
it = PyObject_GC_New(tupleiterobject, &PyTupleIter_Type);
if (it == NULL)
return NULL;
/*
将迭代器的初始索引值设置为0,
这样当第一次进行迭代操作时,就会
将元组中的第一个成员给获取出来了。
*/
it->it_index = 0;
Py_INCREF(seq);
// 为迭代器设置需要进行迭代的元组对象。
it->it_seq = (PyTupleObject *)seq;
_PyObject_GC_TRACK(it);
// 将新的迭代器作为结果返回。
return (PyObject *)it;
}
|
在对元组进行迭代操作时,会先创建一个与当前元组相关的迭代器。该迭代器中有一个it_index字段,该字段表示迭代操作时的索引值。它的初始值为0,这样当第一次进行迭代操作时,就会将元组的索引为0的成员(也就是第一个成员)给获取出来。每执行一次迭代操作,it_index的值还会自动加一,这样反复执行迭代操作后,就可以将元组里的所有成员都给依次获取出来了。
在上一篇介绍列表相关的脚本函数时,我们提到了cmp,len,max,min这些内建函数可以对列表进行操作,其实它们也可以对元组进行操作,下面就对这些内建函数在元组中的使用进行介绍。len函数在上面的
len(tuple) 部分已经讲解过了,下面就不再介绍该函数了。
cmp(tuple1, tuple2):
先来看个简单的例子:
[email protected]:~$ gdb -q python
....................................................
>>> cmp((1,2,3), (3,4,5))
....................................................
Breakpoint 1, tuplerichcompare (v=0xb7c9eab4, w=0xb7c9eef4, op=2)
at Objects/tupleobject.c:575
575 if (!PyTuple_Check(v) || !PyTuple_Check(w)) {
(gdb) backtrace
#0 tuplerichcompare (v=0xb7c9eab4, w=0xb7c9eef4, op=2)
at Objects/tupleobject.c:575
#1 0x08097396 in try_rich_compare (v=0xb7c9eab4, w=0xb7c9eef4, op=2)
at Objects/object.c:622
#2 0x080974cb in try_rich_compare_bool (v=0xb7c9eab4, w=0xb7c9eef4, op=2)
at Objects/object.c:650
#3 0x0809761c in try_rich_to_3way_compare (v=0xb7c9eab4, w=0xb7c9eef4)
at Objects/object.c:684
....................................................
#7 0x080e6e57 in builtin_cmp (self=0x0, args=0xb7d3a84c)
at Python/bltinmodule.c:429
....................................................
(gdb) c
Continuing.
Breakpoint 1, tuplerichcompare (v=0xb7c9eab4, w=0xb7c9eef4, op=0)
at Objects/tupleobject.c:575
....................................................
-1
>>> cmp((1,2,3), (1,2,3))
0
>>> cmp((1,2,3), (1,2))
1
>>>
|
上面的cmp脚本函数会对参数中的两个元组进行比较。当返回-1时表示第一个元组小于第二个元组,返回0时表示两个元组相等,返回1时则表示第一个元组大于第二个元组。
cmp在内部执行时所涉及到的
builtin_cmp,
try_rich_to_3way_compare,
try_rich_compare_bool以及
try_rich_compare这几个C函数,已经在上一篇文章中讲解过了。
try_rich_compare函数最终会通过
tuplerichcompare函数去完成元组的比较操作。
tuplerichcompare函数定义在
Objects/tupleobject.c文件里:
static PyObject *
tuplerichcompare(PyObject *v, PyObject *w, int op)
{
PyTupleObject *vt, *wt;
Py_ssize_t i;
Py_ssize_t vlen, wlen;
// 需要进行比较的两个对象v与w
// 必须都是元组类型。
if (!PyTuple_Check(v) || !PyTuple_Check(w)) {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
// 将v与w转为元组对象指针。
vt = (PyTupleObject *)v;
wt = (PyTupleObject *)w;
// 获取两个元组的尺寸大小。
vlen = Py_SIZE(vt);
wlen = Py_SIZE(wt);
/* Note: the corresponding code for lists has an "early out" test
* here when op is EQ or NE and the lengths differ. That pays there,
* but Tim was unable to find any real code where EQ/NE tuple
* compares don't have the same length, so testing for it here would
* have cost without benefit.
*/
/* Search for the first index where items are different.
* Note that because tuples are immutable, it's safe to reuse
* vlen and wlen across the comparison calls.
*/
// 从两个元组的尺寸范围内,
// 搜索第一个不相等的成员的索引值。
for (i = 0; i < vlen && i < wlen; i++) {
int k = PyObject_RichCompareBool(vt->ob_item[i],
wt->ob_item[i], Py_EQ);
if (k < 0)
return NULL;
if (!k)
break;
}
// 如果在两个元组的尺寸范围内,
// 没有找到不相等的成员的话,
// 则哪个元组的尺寸长,那么该元组就大。
// 例如:(1,2,3) 就大于 (1,2) 。
// 如果两个元组的尺寸相同,则这两个元组就相等。
// 下面的switch...case语句只需要
// 针对不同的op,返回不同的结果即可。
if (i >= vlen || i >= wlen) {
/* No more items to compare -- compare sizes */
int cmp;
PyObject *res;
switch (op) {
case Py_LT: cmp = vlen < wlen; break;
case Py_LE: cmp = vlen <= wlen; break;
case Py_EQ: cmp = vlen == wlen; break;
case Py_NE: cmp = vlen != wlen; break;
case Py_GT: cmp = vlen > wlen; break;
case Py_GE: cmp = vlen >= wlen; break;
default: return NULL; /* cannot happen */
}
if (cmp)
res = Py_True;
else
res = Py_False;
Py_INCREF(res);
return res;
}
// 如果在两个元组的尺寸范围内,
// 找到了不相等的成员,
// 就说明这两个元组不相等。
// 那么当op为Py_EQ(判断两个元组是否相等)时,
// 返回False,
// 当op为Py_NE(判断两个元组是否不相等)时,
// 则返回True
/* We have an item that differs -- shortcuts for EQ/NE */
if (op == Py_EQ) {
Py_INCREF(Py_False);
return Py_False;
}
if (op == Py_NE) {
Py_INCREF(Py_True);
return Py_True;
}
// 当op不为Py_EQ或Py_NE时,
// 就将第一个不相等的成员的比较结果作为
// 这两个元组的比较结果。
// 例如:(1,2,3)与(3,4,5)在进行比较时,
// 这两个元组的第一个成员1和3不相等,
// 那么由于1小于3,
// 因此,(1,2,3)就小于(3,4,5)了。
/* Compare the final item again using the proper operator */
return PyObject_RichCompare(vt->ob_item[i], wt->ob_item[i], op);
}
|
可以看到,上面的
tuplerichcompare函数与上一篇文章中介绍过的
list_richcompare函数,在算法上非常相似,这是因为元组与列表在内部结构上本身就非常相似的缘故。
max(tuple...)与min(tuple...):
下面是max函数的简单例子:
[email protected]:~$ gdb -q python
....................................................
>>> max((123, 234, 345, 478))
....................................................
Breakpoint 4, builtin_max (self=0x0, args=0xb7d45424, kwds=0x0)
at Python/bltinmodule.c:1451
1451 return min_max(args, kwds, Py_GT);
(gdb) s
min_max (args=0xb7d45424, kwds=0x0, op=4) at Python/bltinmodule.c:1348
1348 PyObject *v, *it, *item, *val, *maxitem, *maxval, *keyfunc=NULL;
....................................................
1366 it = PyObject_GetIter(v);
(gdb) s
PyObject_GetIter (o=0xb7d17af4) at Objects/abstract.c:3069
3069 PyTypeObject *t = o->ob_type;
....................................................
(gdb) s
tuple_iter (seq=0xb7d17af4) at Objects/tupleobject.c:1040
1040 if (!PyTuple_Check(seq)) {
....................................................
(gdb) s
PyIter_Next (iter=0xb7ca1b5c) at Objects/abstract.c:3103
3103 result = (*iter->ob_type->tp_iternext)(iter);
(gdb) s
tupleiter_next (it=0xb7ca1b5c) at Objects/tupleobject.c:968
968 assert(it != NULL);
(gdb) c
Continuing.
478
>>>
|
从调试输出中可以看到,
max在内部会通过
bltinmodule.c文件里的
builtin_max进入
min_max函数。在
min_max里,会先通过
PyObject_GetIter调用
tuple_iter来获取与元组相关的迭代器。最后再由
tupleiter_next函数根据迭代器将元组中的每个成员都提取出来进行比较,并将比较得到的值最大的成员作为结果返回。
min内建脚本函数的执行过程也是同理:
[email protected]:~$ gdb -q python
....................................................
>>> min((123, 234, 345, 478))
Breakpoint 5, builtin_min (self=0x0, args=0xb7d45424, kwds=0x0)
at Python/bltinmodule.c:1437
1437 return min_max(args, kwds, Py_LT);
(gdb) s
min_max (args=0xb7d45424, kwds=0x0, op=0) at Python/bltinmodule.c:1348
1348 PyObject *v, *it, *item, *val, *maxitem, *maxval, *keyfunc=NULL;
....................................................
(gdb) s
tuple_iter (seq=0xb7d17af4) at Objects/tupleobject.c:1040
1040 if (!PyTuple_Check(seq)) {
....................................................
(gdb) s
tupleiter_next (it=0xb7ca1a0c) at Objects/tupleobject.c:968
968 assert(it != NULL);
(gdb) c
Continuing.
123
>>>
|
只不过
min在进行迭代比较后,是将值最小的成员对象给获取出来。
min与max在内部最终都是通过min_max这个C函数来完成获取最小值或最大值的操作的,min_max函数在上一篇文章介绍列表的函数时已经详细的讲解过了,这里就不多说了。只不过,上一篇文章中在讲解该函数时,是以列表为例进行说明的,如果换成元组也是一样的,如下例所示:
[email protected]:~$ gdb -q python
....................................................
>>> max((1,2,3), (2,3,4), (3,4,5))
(3, 4, 5)
>>> def myfunc(a):
... if(a == 5):
... return 1
... else:
... return a
...
>>> max((3,4,5), key = myfunc)
4
>>> min((3,4,5), key = myfunc)
5
>>>
|
上面的例子都是上一篇文章中讲解min_max函数时,注释中的例子,只不过这里用的是元组而已。
tuple(seq):
tuple脚本函数可以将可迭代的对象转为元组,如下例所示:
[email protected]:~$ gdb -q python
....................................................
>>> tuple([1,3,5])
....................................................
Breakpoint 6, type_call (type=0x81ca180, args=0xb7c9ff4c, kwds=0x0)
at Objects/typeobject.c:722
722 if (type->tp_new == NULL) {
(gdb) n
729 obj = type->tp_new(type, args, kwds);
(gdb) s
tuple_new (type=0x81ca180, args=0xb7c9ff4c, kwds=0x0)
at Objects/tupleobject.c:647
....................................................
(1, 3, 5)
>>> tuple("abcd")
('a', 'b', 'c', 'd')
>>> tuple((1,2,3,4,5))
(1, 2, 3, 4, 5)
>>>
|
从上例中可以看到:列表,字符串等可迭代的对象都可以被tuple转为元组对象。tuple在内部是通过
tuple_new这个C函数去完成转换工作的(该C函数定义在
Objects/tupleobject.c文件里):
static PyObject *
tuple_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *arg = NULL;
// 可以给tuple提供一个sequence的key,例如:
// >>> tuple(sequence = "abcde")执行后的结果为
// ('a', 'b', 'c', 'd', 'e')
// 该例中tuple(sequence = "abcde")等效于
// tuple("abcde")
static char *kwlist[] = {"sequence", 0};
if (type != &PyTuple_Type)
return tuple_subtype_new(type, args, kwds);
// 从参数中提取出要转换的对象。
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:tuple", kwlist, &arg))
return NULL;
// 如果没提供任何参数,则返回一个空元组。
// 例如:tuple()脚本执行后,
// 得到的就是一个空元组。
if (arg == NULL)
return PyTuple_New(0);
// 将参数对象传递给PySequence_Tuple函数,
// 并由该函数去完成具体的转换工作。
// 该函数定义在Objects/abstract.c文件中。
else
return PySequence_Tuple(arg);
}
|
tuple_new最终是通过PySequence_Tuple去完成转换工作的,限于篇幅,这里不会对PySequence_Tuple函数进行讲解,请读者通过gdb调试器来自行分析。
元组对象所包含的方法:
由于元组对象的成员是固定不变的,因此,元组对象所支持的方法也比列表对象要少很多。我们可以在
tuple_methods数组中查看到元组对象支持的方法(该数组定义在
Objects/tupleobject.c文件里):
static PyMethodDef tuple_methods[] = {
{"__getnewargs__", (PyCFunction)tuple_getnewargs, METH_NOARGS},
{"__sizeof__", (PyCFunction)tuple_sizeof, METH_NOARGS, sizeof_doc},
{"index", (PyCFunction)tupleindex, METH_VARARGS, index_doc},
{"count", (PyCFunction)tuplecount, METH_O, count_doc},
{NULL, NULL} /* sentinel */
};
|
可以看到,元组支持
index与
count两个方法。下面就对这两个方法进行介绍。
元组对象的index方法:
index方法的使用,如下所示:
[email protected]:~$ gdb -q python
....................................................
>>> tuple1 = (123, 'xyz', 'zz', 'a', 'b', 'c', '1')
>>> tuple1.index('a')
....................................................
Breakpoint 7, tupleindex (self=0xb7d1c854, args=0xb7d45424)
at Objects/tupleobject.c:514
....................................................
3
>>> tuple1.index('a',2,5)
3
>>> tuple1.index('a',2,3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: tuple.index(x): x not in tuple
>>>
|
index方法会使用参数对象,在元组中查找成员,当找到与参数对象的值相等的成员时,就将该成员对应的元组索引值作为结果返回。
index方法还可以接受两个额外的可选参数,例如:上面的tuple1.index('a',2,5),这里的2和5就表示在元组的索引2到索引5(不包括5在内)的片段中,搜索'a'。
如果index方法找不到符合条件的成员时,就会抛出
ValueError: tuple.index(x): x not in tuple 的错误。
index方法会调用的底层C函数为
tupleindex,该函数定义在
Objects/tupleobject.c文件中:
static PyObject *
tupleindex(PyTupleObject *self, PyObject *args)
{
Py_ssize_t i, start=0, stop=Py_SIZE(self);
PyObject *v;
// 将需要比较的参数对象设置到v,
// 如果提供了可选参数,则将可选
// 参数设置到start与stop中,
// 作为需要进行搜索的元组片段的
// 起始与结束位置。
// 如果没提供可选参数的话,
// start的默认初始值为0,
// stop的默认初始值则为元组的总长度。
// 后面就会在start到stop的元组索引范围内
// (不包括stop在内)对v进行搜索
if (!PyArg_ParseTuple(args, "O|O&O&:index", &v,
_PyEval_SliceIndex, &start,
_PyEval_SliceIndex, &stop))
return NULL;
// 当start小于0时,就将其加上元组的
// 长度值,从而将其转为有效的索引值。
if (start < 0) {
start += Py_SIZE(self);
if (start < 0)
start = 0;
}
// 同理,将小于0的stop转为有效的索引值。
if (stop < 0) {
stop += Py_SIZE(self);
if (stop < 0)
stop = 0;
}
// 通过for循环,将元组的start到stop
// (不包括stop在内)的范围里的成员与v进行比较。
// 如果从中找到了值相等的成员时,就将
// 该成员对应的元组索引值作为结果返回。
for (i = start; i < stop && i < Py_SIZE(self); i++) {
int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);
if (cmp > 0)
return PyInt_FromSsize_t(i);
else if (cmp < 0)
return NULL;
}
// 如果搜索不到符合条件的成员,则会抛出
// "ValueError: tuple.index(x): x not in tuple"
// 的错误。
PyErr_SetString(PyExc_ValueError, "tuple.index(x): x not in tuple");
return NULL;
}
|
元组对象的count方法:
count方法的使用,如下所示:
[email protected]:~$ gdb -q python
....................................................
>>> tuple1 = (1, 3, 4, 4, 4, 5, 6, 8)
>>> tuple1.count(4)
....................................................
Breakpoint 8, tuplecount (self=0xb7df5e94, v=0x8207b20)
at Objects/tupleobject.c:545
545 Py_ssize_t count = 0;
....................................................
3
>>>
|
可以看到,count方法会将元组中与参数对象的值相等的成员的个数给统计出来。例如,上面的tuple1元组里包含了三个
4,因此,tuple1.count(
4)脚本执行后,返回的结果就是
3。
count方法对应的底层C函数为
tuplecount,该函数也定义在
Objects/tupleobject.c文件中:
static PyObject *
tuplecount(PyTupleObject *self, PyObject *v)
{
Py_ssize_t count = 0;
Py_ssize_t i;
// 这里的self表示需要进行搜索的元组,
// v则表示需要进行统计的参数对象。
// 通过for循环,从索引值0开始,
// 将元组中的所有成员都与v进行比较。
// 每当找到一个与v的值相等的成员时,
// 就将count计数器的值加一。
// 这样就可以将符合条件的成员个数
// 给统计出来了。
for (i = 0; i < Py_SIZE(self); i++) {
int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);
if (cmp > 0)
count++;
else if (cmp < 0)
return NULL;
}
// 将count作为结果返回。
// 此外,由于count的默认初始值为0,
// 因此,当没有找到符合条件的成员时。
// 就会返回0。
return PyInt_FromSsize_t(count);
}
|
结束语:
以上就是和元组相关的内容。下一篇将介绍Dictionary词典相关的内容。
OK,休息,休息一下 o(∩_∩)o~~
我不知道多久才能登上山顶,我只知道我一定要登上山顶。
—— 妒火线