【问题标题】:Python: the mechanism behind list comprehensionPython:列表理解背后的机制
【发布时间】:2011-01-30 16:33:04
【问题描述】:

在 for 循环上下文中使用列表解析或 in 关键字时,即:

for o in X:
    do_something_with(o)

l=[o for o in X]
  • in 背后的机制是如何工作的?
  • 它调用X 中的哪些函数\方法?
  • 如果X 可以遵守不止一种方法,那么优先级是什么?
  • 如何写一个高效的X,让列表理解快?

【问题讨论】:

  • 请注意,“in”关键字在 Python 的两个不同上下文中使用。有迭代(使用“for”键盘)和布尔/条件上下文(有时使用“if”或“while”)。后者为其对象调用 contains 方法。

标签: python implementation list-comprehension language-implementation


【解决方案1】:

afaik,完整且正确的答案。

for,在 for 循环和列表推导中,在 X 上调用 iter()。如果X 具有__iter__ 方法或__getitem__ 方法,iter() 将返回一个可迭代对象。如果两者都实现,则使用__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]

如您所见,您需要实现迭代器和返回迭代器的__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

最后一种是我推荐的方式。简单高效。

【讨论】:

  • +1 很好、全面的答案。也感谢所有的 cmets。
【解决方案2】:

X 必须是可迭代的。它必须实现__iter__(),它返回一个迭代器对象;迭代器对象必须实现next(),它在每次调用时返回下一项,如果没有下一项则引发StopIteration

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

请注意,普通的 for 运算符使用相同的机制。

【讨论】:

  • 迭代器也必须实现__iter__(),尽管它们只能返回对自己的引用
  • 对,生成器就是这样做的。
  • X 可以改为实现__getitem__
【解决方案3】:

回答问题的 cmets 我可以说在这种情况下阅读源代码并不是最好的主意。对于第一次看到 Python 源代码的人来说,负责执行编译代码 (ceval.c) 的代码似乎不是很冗长。这是代表for循环中迭代的sn-p:

   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();

要找到这里实际发生的情况,您需要深入研究一堆其他文件,这些文件的详细程度并没有好多少。因此,我认为在这种情况下,文档和类似 SO 的网站是首选,而应仅检查源代码以了解未发现的实现细节。

【讨论】:

    【解决方案4】:

    X 必须是一个可迭代对象,这意味着它需要有一个__iter__() 方法。

    所以,要开始一个for..in循环,或者一个列表推导式,首先要调用X__iter__()方法来获得一个迭代器对象;然后为每次迭代调用该对象的 next() 方法,直到引发 StopIteration,此时迭代停止。

    我不确定您的第三个问题是什么意思,以及如何为您的第四个问题提供有意义的答案,只是您的迭代器不应一次在内存中构造整个列表。

    【讨论】:

    • 不,它需要有__iter__()__geitem__()
    • @Lennart:谢谢你让我直截了当。
    【解决方案5】:

    也许这会有所帮助(教程http://docs.python.org/tutorial/classes.html 第 9.9 节):

    在幕后,for 语句 在容器对象上调用 iter()。 该函数返回一个迭代器 定义方法 next() 的对象 访问元素中的 一次一个容器。当有 没有更多元素,next() 引发 StopIteration 异常告诉 for 循环终止。

    【讨论】:

      【解决方案6】:

      回答您的问题:

      in 背后的机制如何运作?

      正如其他人已经指出的那样,它与用于普通 for 循环的机制完全相同。

      它调用了 X 中的哪些函数\方法?

      正如下面评论中所述,它调用iter(X) 来获取迭代器。如果X定义了方法函数__iter__(),则调用this返回一个迭代器;否则,如果X 定义了__getitem__(),这将被重复调用以迭代X。在此处查看iter() 的 Python 文档:http://docs.python.org/library/functions.html#iter

      如果 X 可以遵守不止一种方法,那么优先级是什么?

      我不确定您的问题到底是什么,但是 Python 有关于如何解析方法名称的标准规则,并且在此处遵循这些规则。以下是对此的讨论:

      Method Resolution Order (MRO) in new style Python classes

      如何写出高效的X,让列表理解起来很快?

      我建议您阅读更多有关 Python 中的迭代器和生成器的信息。使任何类支持迭代的一种简单方法是为 iter() 创建一个生成器函数。下面是对生成器的讨论:

      http://linuxgazette.net/100/pramode.html

      【讨论】:

      • 不是,它调用iter(),后者又调用__iter__(),或者返回一个使用__getitem__的迭代器。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-02
      • 1970-01-01
      • 2018-03-04
      • 1970-01-01
      • 2021-07-02
      • 1970-01-01
      相关资源
      最近更新 更多