【问题标题】:how to make more expressive python iterators? just like c++ iterator如何制作更具表现力的 Python 迭代器?就像 c++ 迭代器
【发布时间】:2012-04-19 04:45:42
【问题描述】:

首先,我快速回顾一下c++风格的迭代器。例如:

//--- Iterating over vector with iterator.
vector<int> v;
. . .
for (vector<int>::iterator it = v.begin(); it!=v.end(); ++it) {
    cout << *it << endl;
}

它很灵活。更改底层容器类型很容易。例如,您可能稍后决定插入和删除的数量如此之多,以至于列表比向量更有效。它还有许多有用的成员函数。向量的许多成员函数都使用迭代器,例如,赋值、插入或擦除。此外,我们可以双向使用迭代器(如果支持),例如 ++、--。这对于解析类似对象的流很有用。

python的问题是: 1:目前,python for 循环语法不如 c++ for 灵活。 (嗯,更安全) 2:而不是“it != iter.end()”样式,python 将在 next() 没有更多内容时抛出异常。不灵活。

问题一:我上面的想法对吗?

好的。我的问题来了,如何实现一个和c++迭代器一样强大的python迭代器?目前,python for 循环语法不如 c++ for 灵活。我也找到了一些可能的解决方案,比如http://www.velocityreviews.com/forums/t684406-pushback-iterator.html。但它要求用户 push_back 一个东西而不是问迭代器 --.

问题 2:在 python 中实现双向迭代器的最佳方法是什么?就像http://www.cplusplus.com/reference/std/iterator/BidirectionalIterator/。 伪代码如下:

it = v.begin();
while( it!=v.end()) {
    //do sth here

    if (condition1)
        ++it;//suppose this iterator supports ++
    if(condition2)
      --it;//suppose this iterator supports --
}

主要特点是:1)双向,2)更简单的“结束”检查。 “++”或“--”运算符或常用函数无关紧要(无论如何它没有语义差异)。

谢谢,

更新: 我从答案中得到了一些可能的解决方案:

i = 0
while i < len(sequence): # or i < len and some_other_condition
    star_it = sequence[i]
    if condition_one(star_it):
        i += 1
    if condition_two(star_it):
        i = max(i - 1, 0)

但是,与数组不同,列表的随机访问应该是 O(n)。我想python内部的“列表”对象是使用链表之类的东西实现的。因此,这种 while 循环解决方案效率不高。但是,在 C++ 中,我们有“随机迭代器”、“双向迭代器”。我应该如何获得更好的解决方案?谢谢。

【问题讨论】:

  • 您能否举一个具体的例子说明您在Python 中无法做到但在C++ 中可以轻松做到的事情?
  • Python yield + 异常机制非常灵活(这就是我们在函数式编程中所说的“延续”)。学会正确使用它们,你会得到回报。在 Python 中定义复合迭代器比在 C++ 中容易得多。
  • 这个问题太含糊了,目前的形式没有真正的答案。
  • 对于双向迭代,Python 的 collections.deque 可能是你想要的(其中双端队列的头部是“迭代器”并且你使用 deque.rotate() 来“移动迭代器”),但是这是一个相当罕见的用例。对于几乎所有常见情况,Python 列表的使用都比 C++ 向量简单得多。

标签: c++ python for-loop iterator


【解决方案1】:

对于大多数情况,Python 的for 和迭代器是最简单的。这是他们的目标,他们不应该为了灵活性而妥协——他们缺乏灵活性不是问题

对于一些不能使用for 循环的情况,C++ 迭代器可能更简单。但总有一种方法可以在 Python 中实现,它并不比使用 C++ 迭代器复杂很多


如果您需要将推进迭代器与循环分开,只需使用while 循环:

it = iter(obj)

try:
    while True: # or some secondary break condition other than StopIteration
        star_it = next(it)
        if condition_one(star_it):
            star_it = next(it)
except StopIteration:
    pass # exhausted the iterator

我只能想到--it 在 Python 中有意义的两种情况。

第一个是您正在迭代一个序列。在这种情况下,如果您需要倒退,根本不要使用迭代器——只需使用带有while 循环的计数器:

i = 0
while i < len(sequence): # or i < len and some_other_condition
    star_it = sequence[i]
    if condition_one(star_it):
        i += 1
    if condition_two(star_it):
        i = max(i - 1, 0)

第二个是如果你正在迭代一个双向链表。在这种情况下,同样不要使用迭代器——只需正常遍历节点即可:

current = node
while current: # or any break condition
    if condition_one(current):
        current = current.next
    if condition_two(star_it):
        current = current.prev

可能认为这是有道理的,但您不能使用上述任何一种方法的情况是使用像setdict 这样的无序集合。但是,--it 没有在这种情况下有意义。由于集合是无序的,从语义上讲,以前到达的任何项目都是合适的——而不仅仅是实际的前一个项目。

因此,为了知道要返回的正确对象,您需要内存,方法是遍历 mydict.values()tuple(myset) 之类的序列并使用计数器,或者组装一系列先前的值去使用while循环和next,而不是for循环。

【讨论】:

  • @XinlinCao 但是针对这些情况量身定制的解决方案比通用解决方案更易于使用,尤其是当通用解决方案必须针对其他情况变得更复杂时。 Python 有解析工具。至于文件,我看不出迭代器比只调用seek 更好?
  • @XinlinCao:你的用例太模糊了。给出一个具体的真实用例,我们可以用 Python 向您展示一个好的解决方案。
  • @XinlinCao 我们试图告诉你这是个错误的问题。在 Python 中有更好的方法来完成您在 C++ 中使用双向迭代器所做的事情。
  • 对于您的解决方案,while 循环中的每个序列[i] 的复杂度都是 O(N)。这将导致性能问题。这就是为什么我试图找到一个双向迭代器。双向迭代器可以随时向前或向后。它是有效的。 i = 0 while i
  • @XinlinCao 不,sequence[i] 是 O(1)。请参阅wiki.python.org/moin/TimeComplexity 顶部附近的列表 getitem 它在 C 级别作为数组实现,因此它只是从一开始就查找(4 或 8 个字节)* i -- 恒定时间
【解决方案2】:

您提到的几种情况的解决方案:

  1. 您想替换底层容器中的对象。对于字典,遍历键或项,而不仅仅是值:

    for key, value in my_dict.iteritems():
        if conditiion(value):
            my_dict[key] = new_value
    

    列表使用enumerate():

    for index, item in enumerate(my_list):
        if condition(item):
            my_list[index] = new_item
    
  2. 您需要一个具有一个“前瞻”值的迭代器。您可能会使用针对特定情况量身定制的东西,但这里有一个适用于一般情况的方法:

    def iter_with look_ahead(iterable, sentinel=None):
        iterable, it_ahead = itertools.tee(iterable)
        next(it_ahead, None)
        return izip_longest(iterable, it_ahead, fillvalue=sentinel)
    
    for current, look_ahead in iter_with look_ahead(tokens):
        # whatever
    
  3. 您想反向迭代。对支持它的容器使用reversed()

  4. 您想要随机访问。只需将您的可迭代对象转换为列表并使用索引:

    my_list = list(my_iterable)
    

【讨论】:

    【解决方案3】:

    实际上,C++ 迭代器系统并没有那么好。迭代器类似于指针,但它们也有自己的缺点:

    • 奇异值:v.end() 无法安全地取消引用
    • 反转问题:std::for_each(end, begin, func);
    • 不匹配问题:std::for_each(v0.begin(), v2.end(), func);

    Python 方法在这方面要好得多(虽然起初使用异常可能会让人感到惊讶,但它确实有助于定义嵌套迭代器),因为与其名称相反,Python 迭代器更类似于 Range

    Range 的概念比 C++11 引入 range-for 循环结构要好得多:

    for (Object& o: range) {
    }
    

    使用迭代器可以实现的任何事情也可以通过范围实现,尽管可能需要一些时间才能实现,而且对于我们这些受过类似 C++ 指针式迭代器教育的人来说,一些翻译起初似乎是超现实主义者。例如,子范围可以完美表达:

    for (Object& o: slice(range, 2, 9)) {
    }
    

    其中slice 将采用[2, 9) 位置range 内的所有元素。

    因此,与其与您的语言(Python)抗争,不如深入研究它并接受它的风格。对抗一种语言通常是一场失败的战斗,学习它的成语,变得高效。

    【讨论】:

    • 简单地说,我只需要一个双向迭代器。不管我们有这么多语法糖,它看起来很简单,但在 C++ 中却没有那么愚蠢和透明。
    【解决方案4】:

    您可以使用 python 对象实现类似的 C++ 方式:

    class Iterable(object):
      class Iterator(object):
        def __init__(self, father, pos=0):
          self.father = father
          self.pos = pos
    
        def __getitem__(self, pos=0):
          return self.father[self.pos + pos]
    
        def __setitem__(self, pos, value):
          self.father[self.pos + pos] = value
    
        def __iadd__(self, increment):
          self.pos += increment
          return self
    
        def __isub__(self, decrement):
          self.pos -= decrement
          return self
    
        def __ne__(self, other):
          return self.father != other.father or self.pos != other.pos
    
        def __eq__(self, other):
          return not (self != other)
    
      def begin(self):
        return self.Iterator(self)
    
      def end(self):
        return self.Iterator(self, len(self))
    
    class Vector(list, Iterable):
      pass
    
    v = Vector([54, 43, 32, 21])
    
    counter = 0
    it = v.begin()
    print it, it[0]
    while it != v.end():
      counter += 1
      print it[0]
      if counter == 2:
        it += 1;  # suppose this iterator supports ++
      if counter == 1:
        it -= 1;  # suppose this iterator supports --
      it += 1
    

    这将*it 替换为it[0](也类似于C++)和it++ 替换为it += 1,但实际上它几乎保持不变。

    不过,如果你这样做,你就会离开 Python 式的方式;-)

    【讨论】:

      【解决方案5】:

      注意,Python中的list对象是一个数组,所以问题中提到的效率问题其实不是问题。

      【讨论】:

      • 这应该在评论部分而不是解决方案。
      猜你喜欢
      • 2015-05-20
      • 2015-01-01
      • 1970-01-01
      • 2014-10-14
      • 1970-01-01
      • 2013-02-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多