python-for循环原理:for循环修改列表值

866 阅读2分钟

一、for循环修改列表值

>>> magician_list=['Alice','John','Tom']
>>> for m in magician_list:
...     m=m+" the Great"
...
>>> magician_list
['Alice', 'John', 'Tom']
>>> for i in range(len(magician_list)):
...     magician_list[i]=magician_list[i]+" the Great"
...
>>> magician_list
['Alice the Great', 'John the Great', 'Tom the Great']

前者修改无效,后者修改成功,这是为什么?

二、for循环遍历列表原理

for循环遍历过程:现实GET_ITER获取迭代器,然后是FOR_ITER的迭代控制过程。

[GET_ITER]
    v=TOP();  //从运行时栈获得PyListObject对象
    x=PyObject_GetIter(v);  //获得PyListObject对象的itrator
    Py_DECREF(v);
    if(x!=NULL){
        SET_TOP(x); //将PyListObject对象的iterator压入堆栈
        PREDICT(FOR_ITER);
        continue;
    }
    STACKADJ(-1)

[FOR_ITER]
    PREDICTED_WIT_ARG(FOR_ITER);
    case FOR_ITER:
        v=TOP();  //从运行时栈的栈顶获得iterator对象
        x=(*v->ob_type->tp_iternext)(v); //通过iterator对象获得集合中下一个元素对象
        if(x!=NULL){
            PUSH(x) //将获得的元素对象压入运行时栈
            PREDICT(STORE_FAST);
            PREDICT(UNPACK_SEQUENCE);
            continue;
        }//x=NULL,意味着iterator的迭代已经结束
        x=v=POP();
        Py_DECREF(v);
        JUMPBY(oparg);
        continue;

可见,for循环是通过PyListObject的迭代器对象访问PyListObject中的元素的。

(一)迭代器对象创建

迭代器对象数据结构:

typedef struct {
    PyObject_HEAD
    Py_ssize_t it_index;
    PyListObject *it_seq; /* Set to NULL when iterator is exhausted */
} listiterobject;
  • it_seq:指向PyListObject对象;
  • it_index:初始化为0,系当前访问的元素在PyListObject中的序号;

有[GET_ITER]代码段可知,Python通过PyObject_GetIter方法获取迭代器对象。PyObject_GetIter方法是通过调用对象(比如PyLisyObject)对应的的类型对象(比如PyList_Type)的tp_iter域获取迭代器的,类型对象PyList_Type的tp_iter域被设置成list_iter方法。list_iter函数代码如下:

static PyObject *list_iter(PyObject *seq)
{
    listiterobject *it;

    if (!PyList_Check(seq)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    it = PyObject_GC_New(listiterobject, &PyListIter_Type);  //[1]
    if (it == NULL)
        return NULL;
    it->it_index = 0;  //[2]
    Py_INCREF(seq);
    it->it_seq = (PyListObject *)seq; //[3]
    _PyObject_GC_TRACK(it);
    return (PyObject *)it;
}

可见,迭代器对象只是对PyListObject做一个简单的封装,看[1][2][3]处代码。

(二)迭代获取下一个元素

对于PyListObject对象的迭代器,获取下一个元素的操作如下:

static PyObject *listiter_next(listiterobject *it)
{
    PyListObject *seq;
    PyObject *item;

    assert(it != NULL);
    seq = it->it_seq;  //[1]
    if (seq == NULL)
        return NULL;
    assert(PyList_Check(seq));

    if (it->it_index < PyList_GET_SIZE(seq)) {
        item = PyList_GET_ITEM(seq, it->it_index);  //[2]
        ++it->it_index;   //[3]
        Py_INCREF(item);
        return item;     //[4]
    }

    it->it_seq = NULL;
    Py_DECREF(seq);
    return NULL;
}

#define PyList_GET_ITEM(op, i) (((PyListObject *)(op))->ob_item[i]

从listiter_next函数[1][2][3][4]处代码可知,在获取了当前it->it_index对应的PyListObject代之后,it->it_index的值加1,为下一次迭代做准备。可以看到,迭代过程中PyListObject的第i个元素是赋值给item的,所以改变item的值,不会改变第i个元素本身。

回到一开始的问题,以下循环不会改变magician_list的元素,正式因为magician_list[i]在迭代过程中赋值给m,m作为副本其改变不会改变magician_list[i]本身。

>>> for m in magician_list:
...     m=m+" the Great"