如何制作更具表现力的 Python 迭代器?就像 C++ 迭代器一样
how to make more expressive python iterators? just like c++ iterator
首先,我快速回顾了 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 loop语法不如c ++灵活。(嗯,更安全(2:而不是"it != iter.end(("风格,python 会在 next(( 没有更多时抛出异常。它不灵活。
问题1:我上面的想法正确吗?
还行。我的问题来了,如何实现一个像 c++ 迭代器一样强大的 python 迭代器?目前,python for loop语法不如c ++灵活。我还发现了一些可能的解决方案,例如 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 的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
您可能认为这是有意义的,但不能使用上述任何一种方法的情况是使用无序集合,如 set
或 dict
。但是,在这种情况下,--it
没有意义。由于集合是无序的,因此从语义上讲,先前到达的任何项目都是合适的 - 而不仅仅是实际的上一个项目。
因此,为了知道要返回的正确对象,您需要内存,要么通过迭代mydict.values()
或tuple(myset)
等序列并使用计数器,要么通过组合先前值的序列并使用while
循环和next
如上所示而不是for
循环。
您提到的几种情况的解决方案:
-
您希望替换基础容器中的对象。 对于字典,循环访问键或项,而不仅仅是值:
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
-
您需要一个具有一个"前瞻"值的迭代器。 您可能会使用针对特定情况量身定制的东西,但这里有一个针对一般情况的秘诀:
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
-
您希望反向迭代。 对支持它的容器使用
reversed()
。 -
您想要随机访问。 只需将可迭代对象转换为列表并使用索引:
my_list = list(my_iterable)
实际上,C++迭代器系统并不是那么好。迭代器类似于指针,它们有其缺点:
- 奇异值:
v.end()
无法安全地取消引用 - 反转问题:
std::for_each(end, begin, func);
- 不匹配问题:
std::for_each(v0.begin(), v2.end(), func);
方法在这方面要好得多(尽管一开始使用异常可能很令人惊讶,但它确实有助于定义嵌套迭代器(,因为与它的名字相反,Python迭代器更类似于Range
。
Range
的概念比C++11引入范围-for循环结构要好得多:
for (Object& o: range) {
}
任何使用迭代器可能的事情都可以通过范围实现,尽管可能需要一些时间才能实现它,对于我们这些受过类似指针的迭代器教育的人来说,一些翻译起初似乎是超现实主义的C++。例如,子范围可以完美地表示:
for (Object& o: slice(range, 2, 9)) {
}
其中slice
会将所有元素置于[2, 9)
range
内的位置。
因此,与其与你的语言(Python(作斗争,不如进一步深入研究它并接受它的风格。与一种语言作斗争通常是一场失败的战斗,学习它的成语,变得高效。
你可以使用 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[0]
(也模拟C++(代替*it
,用it += 1
代替it++
,但实际上它几乎保持不变。
不过,如果你这样做,你就会离开 Python 的方式;-(
请注意,Python 中的列表对象是一个数组,因此问题中提到的效率问题实际上不是问题。
- 使用std::multimap迭代器创建std::list
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++中带有List类的迭代器Segfault
- 如何在c++迭代器类型中包装std::chrono
- 集合上的输出迭代器:assign和increment迭代器
- Boost Spirit,获取迭代器内部语义动作
- 对于set上的循环-获取next元素迭代器
- 为什么output_editor Concept不需要output_e迭代器标记
- c++17文件系统::recursive_directory迭代器()在mac上没有给出这样的目录,但在windows上
- 使用迭代器时如何访问对象在向量中的位置?
- std::vector::迭代器是否可以合法地作为指针
- 跟随整数索引列表的自定义类迭代器
- 不明白迭代器,引用和指针失效,一个例子
- 我可以使用反向迭代器作为ForwardIt吗
- ESP8266单片机矢量迭代器的C++问题
- 如何在C++中将迭代器作为函数参数传递
- 是否应避免从非常量迭代器转换为常量迭代器?
- 如何在 c++ 中将字符串迭代器变量传递给函数?
- 为什么 vector 的随机访问迭代器给出与指针不同的内存地址?
- 为什么 C++ std::unordered_map 从 emplace/ 找到返回一个迭代器?