Python的元组类型,在内部与列表类型具有非常相似的结构,只不过列表的C结构中还多了一个allocated字段。另外,列表的ob_item数组是可动态调整的,也就是数组的大小及成员是可以被改变的。而元组的ob_item数组则是固定的,其大小及成员都是不可改变的...

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

    本篇文章是根据英文教程《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来完成元组的获取单个成员的具体操作。

    tuplesubscripttupleitem都是定义在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 */
};


    上面结构中的tupleitemtupleslice函数,在前面已经介绍过了。下面再对其他几个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_cmptry_rich_to_3way_comparetry_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 */
};


    可以看到,元组支持indexcount两个方法。下面就对这两个方法进行介绍。

元组对象的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~~

    我不知道多久才能登上山顶,我只知道我一定要登上山顶。

——  妒火线
 
上下篇

下一篇: Python词典类型

上一篇: Python列表相关的脚本函数

相关文章

Python的数字类型及相关的类型转换函数

Python字符串相关的函数

Python循环语句

Python词典类型

Python的基本语法

Python中的异常