# 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 () 的探究就到这了,基本重要的都讲了,有兴趣可以自己翻一翻源码😎