# Python zip()
# 我一般也不清楚如何形容一些方法的特性,都是在网上查查就过了,先看看 runoob 对 zip 的描述
zip() 用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元素组成的列表。如果各个迭代器的元素个数不一致,则返回的列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表
关于 zip 的简单用法可以参考:https://www.runoob.com/python/python-func-zip.html
在 Python 源码中,zip 是这样子的
class zip(object): | |
""" | |
zip(*iterables) --> zip object | |
Return a zip object whose .__next__() method returns a tuple where | |
the i-th element comes from the i-th iterable argument. The .__next__() | |
method continues until the shortest iterable in the argument sequence | |
is exhausted and then it raises StopIteration. | |
""" | |
def __getattribute__(self, *args, **kwargs): # real signature unknown | |
""" Return getattr(self, name). """ | |
pass | |
def __init__(self, *iterables): # real signature unknown; restored from __doc__ | |
pass | |
def __iter__(self, *args, **kwargs): # real signature unknown | |
""" Implement iter(self). """ | |
pass | |
@staticmethod # known case of __new__ | |
def __new__(*args, **kwargs): # real signature unknown | |
""" Create and return a new object. See help(type) for accurate signature. """ | |
pass | |
def __next__(self, *args, **kwargs): # real signature unknown | |
""" Implement next(self). """ | |
pass | |
def __reduce__(self, *args, **kwargs): # real signature unknown | |
""" Return state information for pickling. """ | |
pass |
zip 类定义了__iter__、__next__,说明它是支持迭代操作的,注释提到,当调用 zip 时,它会返回一个 zip 对象,调用.__next__() 方法返回一个元组,其中第 i 个元素来自第 i 个可迭代参数,__next__() 会一直持续到参数序列中最短的可迭代对象用尽为止,然后引发 StopIteration,终止循环
在调用 zip 时,我们可以传递多个 iterator
a = [1, 2, 3] | |
b = [4, 5, 6] | |
c = [7, 8, 9] | |
r = zip(a, b, c) | |
for i in r: | |
print(i) | |
======>>> 打印结果 | |
(1, 4, 7) | |
(2, 5, 8) | |
(3, 6, 9) | |
======>>> |
# 在 CPython 中 zip 的结构
PyTypeObject PyZip_Type = { | |
PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
"zip", /* tp_name */ | |
sizeof(zipobject), /* tp_basicsize */ | |
...... /* 忽略,不重要 */ | |
PyObject_GenericGetAttr, /* tp_getattro */ | |
0, /* tp_setattro */ | |
0, /* tp_as_buffer */ | |
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | | |
Py_TPFLAGS_BASETYPE, /* tp_flags */ | |
zip_doc, /* tp_doc */ | |
(traverseproc)zip_traverse, /* tp_traverse */ | |
0, /* tp_clear */ | |
0, /* tp_richcompare */ | |
0, /* tp_weaklistoffset */ | |
PyObject_SelfIter, /* tp_iter */ | |
(iternextfunc)zip_next, /* tp_iternext */ | |
zip_methods, /* tp_methods */ | |
0, /* tp_members */ | |
0, /* tp_getset */ | |
0, /* tp_base */ | |
0, /* tp_dict */ | |
0, /* tp_descr_get */ | |
0, /* tp_descr_set */ | |
0, /* tp_dictoffset */ | |
0, /* tp_init */ | |
PyType_GenericAlloc, /* tp_alloc */ | |
zip_new, /* tp_new */ | |
PyObject_GC_Del, /* tp_free */ | |
}; |
那在我们调用 zip 时,是先调用了 zip_new 方法
typedef struct { | |
PyObject_HEAD | |
Py_ssize_t tuplesize; | |
PyObject *ittuple; /* tuple of iterators */ | |
PyObject *result; | |
} zipobject; //zipobject 结构体 | |
static PyObject * | |
zip_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
{ | |
zipobject *lz; // 成功创建则返回对象,对应 Python 中的 zip object | |
Py_ssize_t i; | |
PyObject *ittuple; /* tuple of iterators */ // 存储每个迭代器的对象 | |
PyObject *result; | |
Py_ssize_t tuplesize; | |
if (type == &PyZip_Type && !_PyArg_NoKeywords("zip", kwds)) | |
// 类型检查和关键字判断 | |
return NULL; | |
/* args must be a tuple */ | |
assert(PyTuple_Check(args)); //args 必须是一个元组 | |
tuplesize = PyTuple_GET_SIZE(args); // 获取 args 的大小 | |
/* obtain iterators */ | |
// 开始获取迭代器 | |
/* | |
如果 args 为空,也就是 zip () 没有参数,那么 tuplesize 就为 0, 会从缓冲池里获取一个空元组 | |
如果不为空,就按照 tuplesize 申请一个元组,并对其元素都设置为 NULL、GC 管理 | |
*/ | |
ittuple = PyTuple_New(tuplesize); | |
if (ittuple == NULL) | |
// 申请失败 | |
return NULL; | |
for (i=0; i < tuplesize; ++i) { //tuplesize 循环 | |
PyObject *item = PyTuple_GET_ITEM(args, i); // 获取 args 对应下标的对象 | |
PyObject *it = PyObject_GetIter(item); // 调用每个 item 对象的__iter__方法,获取 iterator(item 可能是不同类型) | |
if (it == NULL) { | |
// 如果某一个 item 调用__iter__后返回空,则运行结束 | |
Py_DECREF(ittuple); // 引用计数 - 1 | |
return NULL; | |
} | |
PyTuple_SET_ITEM(ittuple, i, it); // 根据下标 i 在 ittuple 中设置 it(迭代器对象) | |
} | |
/* create a result holder */ | |
// 再根据 tuplesize 创建一个元组,专门用来存储结果,而大小和 ittuple 一样 | |
result = PyTuple_New(tuplesize); | |
if (result == NULL) { | |
// 申请失败 | |
Py_DECREF(ittuple); | |
return NULL; | |
} | |
for (i=0 ; i < tuplesize ; i++) { | |
Py_INCREF(Py_None); // 空对象 | |
PyTuple_SET_ITEM(result, i, Py_None); // 根据下标 i 在 result 中设置 None | |
} | |
/* create zipobject structure */ | |
lz = (zipobject *)type->tp_alloc(type, 0); | |
if (lz == NULL) { | |
Py_DECREF(ittuple); | |
Py_DECREF(result); | |
return NULL; | |
} | |
lz->ittuple = ittuple; // 设置迭代器元素 | |
lz->tuplesize = tuplesize; // 设置大小 | |
lz->result = result; // 设置存储结果的元组 | |
return (PyObject *)lz; // 返回 zipobject | |
} |
在调用 zip () 时,首先根据传递的位置参数 args,获取 args 的大小,然后根据该大小申请两个元组,一个用来存储位置参数中,每个对象调用__iter__() 后返回的迭代器对象,一个用来存储调用__next__时返回的结果,一共执行了两次循环,最终返回 zipobject
g = zip() | |
print(g) | |
<zip object at 0x0000020D3976D200> |
在对 zip object 进行迭代时,是如何处理的,首先会先调用 zip object 的__iter__方法,而它返回了本身
PyObject * | |
PyObject_SelfIter(PyObject *obj) | |
{ | |
Py_INCREF(obj); | |
return obj; | |
} |
并对本身不断的调用__next__方法
static PyObject * | |
zip_next(zipobject *lz) | |
{ | |
Py_ssize_t i; | |
Py_ssize_t tuplesize = lz->tuplesize; // 元素的大小 | |
PyObject *result = lz->result; // 存储结果的元组 | |
PyObject *it; | |
PyObject *item; | |
PyObject *olditem; | |
if (tuplesize == 0) | |
return NULL; | |
if (Py_REFCNT(result) == 1) { | |
// 当引用计数为 1 时 | |
Py_INCREF(result); // 引用计数 + 1 | |
// 对 tuplesize 进行循环 | |
for (i=0 ; i < tuplesize ; i++) { | |
it = PyTuple_GET_ITEM(lz->ittuple, i); // 根据 i 获取 ittuple 对应下标的迭代器 | |
item = (*Py_TYPE(it)->tp_iternext)(it); // 调用每个迭代器的__next__方法,获取第 it 位置的元素 | |
if (item == NULL) { | |
// 如果__next__返回空,表示可迭代对象已用尽,调用结束 | |
// 当 zip () 传递的每个参数长度不一致,就会以最短的迭代完并结束 | |
// 比如 a = [1,2,3], b = [4,5,6,7,8,9], 当执行第 4 次时,a 已经用尽了,根据下标 4 在 a 中获取会发生异常,也就会结束 | |
Py_DECREF(result); | |
return NULL; | |
} | |
// 根据下标 i 在 result (结果元组) 里获取 None, 对 None 进行引用计数 - 1, 并在它的位置设置每个迭代器返回的元素 | |
olditem = PyTuple_GET_ITEM(result, i); | |
PyTuple_SET_ITEM(result, i, item); // 在 None 的位置设置 item | |
Py_DECREF(olditem); | |
} | |
// bpo-42536: The GC may have untracked this result tuple. Since we're | |
// recycling it, make sure it's tracked again: | |
// 一些 GC 的判断处理 | |
if (!_PyObject_GC_IS_TRACKED(result)) { | |
_PyObject_GC_TRACK(result); | |
} | |
} else { | |
// 当结果元组的引用计数不为 1, 就重新申请一个 | |
result = PyTuple_New(tuplesize); | |
if (result == NULL) | |
// 申请失败 | |
return NULL; | |
for (i=0 ; i < tuplesize ; i++) { // 这里的循环处理跟上面的相差无几,只是不需要管理 None 了 | |
it = PyTuple_GET_ITEM(lz->ittuple, i); // 根据 i 下标在 ittuple 中获取每个迭代器 | |
item = (*Py_TYPE(it)->tp_iternext)(it); // 调用每个迭代器的__next__方法返回元素 | |
if (item == NULL) { | |
// 元素为 NULL 时表示迭代结束 | |
Py_DECREF(result); | |
return NULL; | |
} | |
PyTuple_SET_ITEM(result, i, item); // 根据 i 下标在结果元组里设置每个 item | |
} | |
} | |
return result; // 返回了一个元组对象 | |
} |
这里需要注意,在对 zip 进行调用__next__时,最终返回了一个 PyTupleObject,也就是元组,在元组里边已经将迭代器的元素都设置到了该元组,所以实际上,返回的每个结果是通过对 PyTupleObject 调用__next__获取的
首先在调用 PyTupleObject 的__iter__时,会将 it_index 设置为 0,也就是起始位置,然后返回一个 tupleiterobject 对象,调用的也正在它的__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)); | |
// 从 0 开始,条件为元组的长度 | |
if (it->it_index < PyTuple_GET_SIZE(seq)) { | |
item = PyTuple_GET_ITEM(seq, it->it_index); // 根据下标获取我们在 zip_next 方法中设置好的每个结果元组 | |
++it->it_index; // 下标 + 1 | |
Py_INCREF(item); // 引用计数 + 1 | |
return item; // 返回元素 | |
} | |
it->it_seq = NULL; | |
Py_DECREF(seq); | |
return NULL; | |
} |
关于 zip () 的探究就到这了,基本重要的都讲了,有兴趣可以自己翻一翻源码😎