一、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"