Python:列表推导背后的机制
当你在使用列表推导式或者在for循环中用到in
这个关键词时,比如:
for o in X:
do_something_with(o)
或者
l=[o for o in X]
in
的工作原理是什么?- 在
X
中,它调用了哪些函数或方法? - 如果
X
可以符合多个方法,优先级是怎样的? - 怎样写一个高效的
X
,让列表推导式运行得更快?
6 个回答
针对问题的评论,我想说在这种情况下直接看源代码并不是最好的主意。负责执行编译代码的部分(ceval.c)对于第一次接触Python源代码的人来说,可能并不太容易理解。下面这段代码是关于for循环中迭代的部分:
TARGET(FOR_ITER)
/* before: [iter]; after: [iter, iter()] *or* [] */
v = TOP();
/*
Here tp_iternext corresponds to next() in Python
*/
x = (*v->ob_type->tp_iternext)(v);
if (x != NULL) {
PUSH(x);
PREDICT(STORE_FAST);
PREDICT(UNPACK_SEQUENCE);
DISPATCH();
}
if (PyErr_Occurred()) {
if (!PyErr_ExceptionMatches(
PyExc_StopIteration))
break;
PyErr_Clear();
}
/* iterator ended normally */
x = v = POP();
Py_DECREF(v);
JUMPBY(oparg);
DISPATCH();
要想真正明白这里发生了什么,你需要深入查看一堆其他文件,而这些文件的可读性也不会好到哪里去。因此,我认为在这种情况下,文档和像Stack Overflow这样的网站是最好的起点,而源代码应该只在需要了解具体实现细节时再去查看。
X
必须是可迭代的。这意味着它需要有一个叫做 __iter__()
的方法,这个方法会返回一个迭代器对象;而这个迭代器对象需要有一个 next()
方法,每次调用时都会返回下一个项目,如果没有下一个项目了,就会抛出一个 StopIteration
的错误。
列表、元组和生成器都是可迭代的。
需要注意的是,普通的 for
循环也是使用同样的机制。
这是我所知道的完整且正确的答案。
for
循环,无论是在循环中还是在列表推导式中,都会对 X
调用 iter()
。iter()
会返回一个可迭代对象,如果 X
有 __iter__
方法或者 __getitem__
方法。如果两个方法都有,优先使用 __iter__
。如果两个方法都没有,你会遇到错误:TypeError: 'Nothing' object is not iterable
。
下面是一个实现 __getitem__
的例子:
class GetItem(object):
def __init__(self, data):
self.data = data
def __getitem__(self, x):
return self.data[x]
使用方法:
>>> data = range(10)
>>> print [x*x for x in GetItem(data)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
这是一个实现 __iter__
的例子:
class TheIterator(object):
def __init__(self, data):
self.data = data
self.index = -1
# Note: In Python 3 this is called __next__
def next(self):
self.index += 1
try:
return self.data[self.index]
except IndexError:
raise StopIteration
def __iter__(self):
return self
class Iter(object):
def __init__(self, data):
self.data = data
def __iter__(self):
return TheIterator(data)
使用方法:
>>> data = range(10)
>>> print [x*x for x in Iter(data)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
正如你所看到的,要实现一个迭代器,你需要同时实现 __getitem__
和返回迭代器的 __iter__
。
你可以把它们结合起来:
class CombinedIter(object):
def __init__(self, data):
self.data = data
def __iter__(self):
self.index = -1
return self
def next(self):
self.index += 1
try:
return self.data[self.index]
except IndexError:
raise StopIteration
使用方法:
>>> well, you get it, it's all the same...
但这样的话,你一次只能有一个迭代器在运行。好吧,在这种情况下,你可以这样做:
class CheatIter(object):
def __init__(self, data):
self.data = data
def __iter__(self):
return iter(self.data)
但这有点不太正当,因为你只是重复使用了 list
的 __iter__
方法。更简单的方法是使用 yield,把 __iter__
变成一个生成器:
class Generator(object):
def __init__(self, data):
self.data = data
def __iter__(self):
for x in self.data:
yield x
我推荐最后这种方法。简单又高效。