Python:列表推导背后的机制

6 投票
6 回答
4105 浏览
提问于 2025-04-16 10:54

当你在使用列表推导式或者在for循环中用到in这个关键词时,比如:

for o in X:
    do_something_with(o)

或者

l=[o for o in X]
  • in的工作原理是什么?
  • X中,它调用了哪些函数或方法?
  • 如果X可以符合多个方法,优先级是怎样的?
  • 怎样写一个高效的X,让列表推导式运行得更快?

6 个回答

4

针对问题的评论,我想说在这种情况下直接看源代码并不是最好的主意。负责执行编译代码的部分(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这样的网站是最好的起点,而源代码应该只在需要了解具体实现细节时再去查看。

5

X 必须是可迭代的。这意味着它需要有一个叫做 __iter__() 的方法,这个方法会返回一个迭代器对象;而这个迭代器对象需要有一个 next() 方法,每次调用时都会返回下一个项目,如果没有下一个项目了,就会抛出一个 StopIteration 的错误。

列表、元组和生成器都是可迭代的。

需要注意的是,普通的 for 循环也是使用同样的机制。

10

这是我所知道的完整且正确的答案。

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

我推荐最后这种方法。简单又高效。

撰写回答