| 方法名 | 返回值 | 描述 |
|---|---|---|
| count() | integer | 返回数据项在T中出现的次数 |
| index() | integer | 返回第一个数据项在T中出现位置的索引,若值不存在,则抛出ValueError |
基础公用函数:
| 函数名 | 返回值 | 描述 |
|---|---|---|
| len() | integer | 返回容器中的项目数 |
| enumerate() | iterator for index, value of iterable | 返回一个可迭代对象,其中以小元组的形式包裹数据项与正向索引的对应关系 |
| reversed() | ... | 详情参见函数章节 |
| sorted() | ... | 详情参见函数章节 |
使用len()方法来获取元组的长度。
返回int类型的值。
tup = ("A", "B", "C", "D", "E", "F", "G")
print(len(tup))
# 7
Python在对内置的数据类型使用len()方法时,实际上是会直接的从PyVarObject结构体中获取ob_size属性,这是一种非常高效的策略。
PyVarObject是表示内存中长度可变的内置对象的C语言结构体。
直接读取这个值比调用一个方法要快很多。
使用count()方法统计数据项在该元组中出现的次数。
返回int:
tup = ("A", "B", "C", "D", "E", "F", "G", "A")
aInTupCount = tup.count("A")
print(aInTupCount)
# 2
使用index()方法找到数据项在当前元组中首次出现的位置索引值,如数据项不存在则抛出异常。
返回int。
tup = ("A", "B", "C", "D", "E", "F", "G", "A")
aInTupIndex = tup.index("A")
print(aInTupIndex)
# 0
Python内部实现中,列表和元组还是有一定的差别的。
元组在创建对象申请内存的时候,内存空间大小便进行了固定,后续不可更改(如果是传入了一个可迭代对象,例如tupe(range(100)),这种情况会进行扩容与缩容,下面的章节将进行探讨研究)。
而列表在创建对象申请内存的时候,内存空间大小不是固定的,如果后续对其新增或删除数据项,列表会进行扩容或者缩容机制。
空元组
若创建一个空元组,会直接进行创建,然后将这个空元组丢到缓存free_list中。
元组的free_list最多能缓存 20 * 2000 个元组,这个在下面会进行讲解。
如图所示:

这样的代码会进行元组转元组:
tup = tuple((1, 2, 3))
首先内部本身就是一个元组(1, 2, 3),所以会直接将内部的这个元组拿出来并返回引用,并不会再次创建。
代码验证:
>>> oldTup = (1, 2, 3) >>> id(oldTup) 4384908128 >>> newTup = tuple(oldTup) >>> id(newTup) 4384908128 >>>
列表转元组会将列表中的每一个数据项都拿出来,然后放入至元组中:
tup = tuple([1, 2, 3])
所以你会发现,列表和元组中的数据项引用都是相同的:
>>> li1 = ["A", "B", "C"] >>> tup = tuple(li1) >>> print(id(li1[0])) 4383760656 >>> print(id(tup[0])) 4383760656 >>>
可迭代对象是没有长度这一概念的,如果是可迭代对象转换为元组,会先对可迭代对象的长度做一个猜想。
并且根据这个猜想,为元组开辟一片内存空间,用于存放可迭代对象的数据项。
然后内部会获取可迭代对象的迭代器,对其进行遍历操作,拿出数据项后放至元组中。
如果猜想的长度太小,会导致元组内部的内存不够存放下所有的迭代器数据项,此时该元组会进行内部的扩容机制,直至可迭代对象中的数据项全部被添加至元组中。
rangeObject = range(1, 101) tup = tuple(rangeObject) // 假如猜想的是9 // 第一步:+ 10 // 第二步:+ (原长度+10) * 0.25 // 其实,就是增加【原长度*0.25 + 2.5】
如果猜想的长度太大,而实际上迭代器中的数据量偏少,则需要对该元组进行缩容。
对元组进行切片取值的时候,会开辟一个新元组用于存放切片后得到的数据项。
tup = (1, 2, 3) newSliceTup = tup[0:2]
当然,如果是[:]的操作,则参照绝对引用,直接返回被切片的元组引用。
代码验证:
>>> id(tup) 4384908416 >>> newSliceTup = tup[0:2] >>> id(newSliceTup) 4384904392
free_list缓存
元组的缓存机制和列表的缓存机制不同。
元组的free_list会缓存0 - 19长度的共20种元组,其中每一种长度的元组通过单向链表横向扩展缓存至2000个,如下图所示:

当每一次的del操作有数据项的元组时,都会将该元组数据项清空并挂载至free_list单向链表的头部的位置。
del 元组1 del 元组2 del 元组3
如下图所示:

当要创建一个元组时,会通过创建元组的长度,从free_list单向链表的头部取出一个元组,然后将数据项存放进去。
前提是free_list单向链表中缓存的有该长度的元组。
tup = (1, 2, 3)

空元组的缓存是一经创建就缓存到free_list单向链表中。
而非空元组的缓存必须是del操作后才缓存到free_list单向链表中。
第一次创建空元组后,空元组会缓存至free_list单向链表中。
以后的每一次空元组创建,返回的其实都是同一个引用,也就是说空元组在free_list单向链表中即使被引用了也不会被销毁。
>>> t1 = () >>> id(t1) 4511088712 >>> t2 = () >>> id(t2) 4511088712
当free_list单向链表中有相同长度的元组时,会进行引用并删除。
这个在上图中已经示例过了,就是这个:

代码示例:
$ python3 Python 3.6.8 (v3.6.8:3c6b436a57, Dec 24 2018, 02:04:31) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> v1 = (None, None, None) >>> id(v1) 4384907696 >>> v2 = (None, None, None) >>> id(v2) 4384908056 >>> del v1 >>> del v2 # ① >>> v3 = (None, None, None) >>> id(v3) # ② 4384908056 >>> v4 = (None, None, None) >>> id(v4) # ③ 4384907696 >>>
①:free_list num_free=3 单向链表结构:v2 —> v1
②:创建了v3,拿出v2的空元组,填入v3数据项,故v2和v3的id值相等,证明引用同一个元组,此时free_list num_free=3 单向链表结构为:—> v1
③:创建了v4,拿出v1的空元组,填入v4数据项,故v1和v4的id值相等,证明引用同一个元组
官网参考:点我跳转
源码一览:点我跳转
以下是截取了一些关键性源代码,并且做上了中文注释,方便查阅。
每一个元组都有几个关键性的属性:
Py_ssize_t ob_refcnt; // 引用计数器 Py_ssize_t ob_size; // 数据项个数,即元组大小 PyObject *ob_item[1]; // 存储元组中的数据项 [指针, ]
关于缓存free_list的属性:
PyTuple_MAXSAVESIZE // 相当于图中的 free_num ,最大20,即纵向扩展的缓存元组长度 PyTuple_MAXFREELIST // 图中 free_list 的横向扩展缓存列表个数,最大2000
空元组
PyObject *
PyTuple_New(Py_ssize_t size)
{
PyTupleObject *op;
// 缓存相关
Py_ssize_t i;
// 元组的大小不能小于0
if (size 0) {
PyErr_BadInternalCall();
return NULL;
}
#if PyTuple_MAXSAVESIZE > 0
// 创建空元组,优先从缓存中获取
// size = 0 表示这是一个空元组,从free_list[0]中获取空元组
if (size == 0 free_list[0]) {
// op就是空元组
op = free_list[0];
// 新增空元组引用计数器 + 1
Py_INCREF(op);
#ifdef COUNT_ALLOCS
tuple_zero_allocs++;
#endif
// 返回空元组的指针
return (PyObject *) op;
}
// 如果创建的不是空元组,且这个创建的元组数据项个数小于20,并且free_list[size]不等于空,表示有缓存
// 则从缓存中去获取,不再重新开辟内存
if (size PyTuple_MAXSAVESIZE (op = free_list[size]) != NULL) {
// 拿出元组
free_list[size] = (PyTupleObject *) op->ob_item[0];
// num_free减1
numfree[size]--;
#ifdef COUNT_ALLOCS
fast_tuple_allocs++;
#endif
/* Inline PyObject_InitVar */
// 初始化,定义这个元组的长度为数据项个数
#ifdef Py_TRACE_REFS
Py_SIZE(op) = size;
// 定义类型为 tuple
Py_TYPE(op) = PyTuple_Type;
#endif
// 增加一次新的引用
_Py_NewReference((PyObject *)op);
}
// 如果是空元组
else
#endif
{
// 检查内存情况,是否充足
/* Check for overflow */
if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - sizeof(PyTupleObject) -
sizeof(PyObject *)) / sizeof(PyObject *)) {
return PyErr_NoMemory();
}
// 开辟内存,并获得一个元组:op
op = PyObject_GC_NewVar(PyTupleObject, PyTuple_Type, size);
if (op == NULL)
return NULL;
}
// 空元组的每一个槽位都是NULL
for (i=0; i size; i++)
op->ob_item[i] = NULL;
#if PyTuple_MAXSAVESIZE > 0
// 缓存空元组
if (size == 0) {
free_list[0] = op;
++numfree[0];
Py_INCREF(op); /* extra INCREF so that this is never freed */
}
#endif
#ifdef SHOW_TRACK_COUNT
count_tracked++;
#endif
// 将元组加入到GC机制中,用于内存管理
_PyObject_GC_TRACK(op);
return (PyObject *) op;
}
这个不在tupleobject.c源码中,而是在abstract.c源码中。
官网参考:点我跳转
源码一览:点我跳转
PyObject *
PySequence_Tuple(PyObject *v)
{
PyObject *it; /* iter(v) */
Py_ssize_t n; /* guess for result tuple size */
PyObject *result = NULL;
Py_ssize_t j;
if (v == NULL) {
return null_error();
}
/* Special-case the common tuple and list cases, for efficiency. */
// 如果是元组转换元组,如 tup = (1, 2, 3) 或者 tup = ((1, 2, 3))直接返回内存地址
if (PyTuple_CheckExact(v)) {
Py_INCREF(v);
return v;
}
// 如果是列表转换元组,则执行PyList_AsTuple(),将列表转换为元组
// 如 tup = ([1, 2, 3])
if (PyList_CheckExact(v))
return PyList_AsTuple(v);
/* Get iterator. */
// 获取迭代器, tup = (range(1, 4).__iter__())
it = PyObject_GetIter(v);
if (it == NULL)
return NULL;
/* Guess result size and allocate space. */
// 猜想迭代器长度,也就是猜一下有多少个数据项
n = PyObject_LengthHint(v, 10);
if (n == -1)
goto Fail;
// 根据猜想的迭代器长度,进行元组的内存开辟
result = PyTuple_New(n);
if (result == NULL)
goto Fail;
/* Fill the tuple. */
// 将迭代器中每个数据项添加至元组中
for (j = 0; ; ++j) {
PyObject *item = PyIter_Next(it);
if (item == NULL) {
if (PyErr_Occurred())
goto Fail;
break;
}
//如果迭代器中数据项比猜想的多,则证明开辟内存不足需要需要进行扩容
if (j >= n) {
size_t newn = (size_t)n;
/* The over-allocation strategy can grow a bit faster
than for lists because unlike lists the
over-allocation isn't permanent -- we reclaim
the excess before the end of this routine.
So, grow by ten and then add 25%.
*/
// 假如猜想的是9
// 第一步:+ 10
// 第二步:+ (原长度+10) * 0.25
// 其实,就是增加【原长度*0.25 + 2.5】
newn += 10u;
newn += newn >> 2;
// 判断是否超过了元组的数据项个数限制(sys.maxsize)
if (newn > PY_SSIZE_T_MAX) {
/* Check for overflow */
PyErr_NoMemory();
Py_DECREF(item);
goto Fail;
}
n = (Py_ssize_t)newn;
// 扩容机制
if (_PyTuple_Resize(result, n) != 0) {
Py_DECREF(item);
goto Fail;
}
}
// 将数据项放入元组之中
PyTuple_SET_ITEM(result, j, item);
}
/* Cut tuple back if guess was too large. */
// 如果猜想的数据项太多,而实际上迭代器中的数据量偏少
// 则需要对该元组进行缩容
if (j n
_PyTuple_Resize(result, j) != 0)
goto Fail;
Py_DECREF(it);
return result;
Fail:
Py_XDECREF(result);
Py_DECREF(it);
return NULL;
}
这个不在tupleobject.c源码中,而是在listobject.c源码中。
官网参考:点我跳转
源码一览:点我跳转
PyObject *
PyList_AsTuple(PyObject *v)
{
PyObject *w;
PyObject **p, **q;
Py_ssize_t n;
// 例如:tup = ([1, 2, 3])
// 进行列表的验证
if (v == NULL || !PyList_Check(v)) {
PyErr_BadInternalCall();
return NULL;
}
// 获取大小,即数据项个数
n = Py_SIZE(v);
// 开辟内存
w = PyTuple_New(n);
// 如果是空元组
if (w == NULL)
return NULL;
// 执行迁徙操作
p = ((PyTupleObject *)w)->ob_item;
q = ((PyListObject *)v)->ob_item;
// 将列表中数据项的引用,也给元组进行引用
// 这样列表中数据项和元组中的数据项都引用同1个对象
while (--n >= 0) {
// 数据项引用计数 + 1
Py_INCREF(*q);
*p = *q;
p++;
q++;
}
// 返回元组
return w;
}
PyObject *
PyTuple_GetSlice(PyObject *op, Py_ssize_t i, Py_ssize_t j)
// 切片会触发该方法
{
// 如果对空元组进行切片,则会抛出异常
if (op == NULL || !PyTuple_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
// 内部的具体实现方法
return tupleslice((PyTupleObject *)op, i, j);
}
static PyObject *
tupleslice(PyTupleObject *a, Py_ssize_t ilow,
Py_ssize_t ihigh)
{
PyTupleObject *np;
PyObject **src, **dest;
Py_ssize_t i;
Py_ssize_t len;
// 计算索引位置
if (ilow 0)
ilow = 0;
if (ihigh > Py_SIZE(a))
ihigh = Py_SIZE(a);
if (ihigh ilow)
ihigh = ilow;
// 如果是[:]的操作,则直接返回源元组对象a的指针,即绝对引用
if (ilow == 0 ihigh == Py_SIZE(a) PyTuple_CheckExact(a)) {
Py_INCREF(a);
return (PyObject *)a;
}
// 初始化新的切片对象元组长度
len = ihigh - ilow;
// 开始切片,创建了一个新元组np
np = (PyTupleObject *)PyTuple_New(len);
if (np == NULL)
return NULL;
src = a->ob_item + ilow;
dest = np->ob_item;
// 对源元组中的数据项的引用计数+1
for (i = 0; i len; i++) {
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
// 返回切片对象新元组np的引用
return (PyObject *)np;
}
static void
tupledealloc(PyTupleObject *op)
{
Py_ssize_t i;
Py_ssize_t len = Py_SIZE(op);
PyObject_GC_UnTrack(op);
Py_TRASHCAN_SAFE_BEGIN(op)
// 如果元组的长度大于0,则不是一个非空元组
if (len > 0) {
i = len;
// 将内部的数据项引用计数都 - 1
while (--i >= 0)
Py_XDECREF(op->ob_item[i]);
#if PyTuple_MAXSAVESIZE > 0
// 准备缓存,判断num_free是否小于20,并且单向链表中的已缓存元组个数小于2000
if (len PyTuple_MAXSAVESIZE
numfree[len] PyTuple_MAXFREELIST
Py_TYPE(op) == PyTuple_Type)
{
// 添加至链表头部
op->ob_item[0] = (PyObject *) free_list[len];
// 将num_free + 1
numfree[len]++;
free_list[len] = op;
goto done; /* return */
}
#endif
}
// 内存中进行销毁
Py_TYPE(op)->tp_free((PyObject *)op);
done:
Py_TRASHCAN_SAFE_END(op)
}
以上就是老Python带你从浅入深探究Tuple的详细内容,更多关于Python Tuple的资料请关注脚本之家其它相关文章!