TL; DR是我试图为基于收益的发电机做得太复杂了吗?
我有一个python应用程序,我需要在一个对象列表上重复一次昂贵的测试,一次一个,然后修改那些通过的对象。我希望有几个对象可以传递,但是我不想创建所有传递的对象的列表,因为mangle会改变其他一些对象的状态。无需按任何特定顺序进行测试。然后冲洗并重复直至某些停止状态。
我的第一个简单实现就是这个,它在逻辑上正确运行
while not stop_condition:
for object in object_list:
if test(object):
mangle(object)
break
else:
handle_no_tests_passed()
遗憾的是,for object in object_list:
始终在列表的开头重新启动,其中对象可能尚未更改,并且列表末尾的对象已准备好进行测试。随机挑选它们会稍微好一点,但我宁愿继续前一段时间从前一段时间开始。我仍然希望for / in调用在它遍历整个列表时终止。
这听起来像是一份收益的工作,但是我把我的大脑绑在一起,没能让它按照我的意愿行事。我可以在简单的情况下使用它,迭代一个范围或从某些来源返回过滤的记录,但我无法找到如何使其保存状态并从源头重新开始读取。
我经常可以用类很长的罗嗦的方式做事,但却不明白如何使用像yield这样的简化。这是一个完全符合我想要的解决方案。
class CyclicSource:
def __init__(self, source):
self.source = source
self.pointer = 0
def __iter__(self):
# reset how many we've done, but not where we are
self.done_this_call = 0
return self
def __next__(self):
ret_val = self.source[self.pointer]
if self.done_this_call >= len(self.source):
raise StopIteration
self.done_this_call += 1
self.pointer += 1
self.pointer %= len(self.source)
return ret_val
source = list(range(5))
q = CyclicSource(source)
print('calling once, aborted early')
count = 0
for i in q:
count += 1
print(i)
if count>=2:
break
else:
print('ran off first for/in')
print('calling again')
for i in q:
print(i)
else:
print('ran off second for/in')
演示了所需的行为
calling once, aborted early
0
1
calling again
2
3
4
0
1
ran off second for/in
最后,问题。是否可以使用yield来简化生成器语法来执行我想要的操作,或者在连续的for / in调用之间保持状态是否需要完整的类语法?
答案 0 :(得分:1)
使用__iter__
方法会导致重置迭代器。这实际上与迭代器的常规行为相反; __iter__
method should just return self
, nothing more。每次创建for
循环时,您都依赖于iter()
将for i in q:
应用于.reset()
的副作用。这使得你的迭代器工作,但行为是令人惊讶并将绊倒未来的维护者。我希望将该效果拆分为单独的def cyclic_source(source):
pointer = 0
done_this_call = 0
while done_this_call < len(source):
ret_val = source[pointer]
done_this_call += 1
pointer = (pointer + 1) % len(source)
reset = yield ret_val
if reset is not None:
done_this_call = 0
yield # pause again for next iteration sequence
方法,例如。
你也可以重置一个生成器,使用generator.send()
发信号通知它重置:
q = cyclic_source(source)
for count, i in enumerate(q):
print(i)
if count == 1:
break
else:
print('ran off first for/in')
print('explicitly resetting the generator')
q.send(True)
for i in q:
print(i)
else:
print('ran off second for/in')
现在您可以将您的点数重置为零:
from itertools import cycle, islice
q = cycle(source)
for count, i in enumerate(islice(q, len(source))):
print(i)
if count == 1:
break
else:
print('ran off first for/in')
for i in islice(q, len(source)):
print(i)
else:
print('ran off second for/in')
然而,这是与可读性相反的。我会使用itertools.cycle()
来使用无限生成器,但itertools.islice()
的迭代次数有限:
q
source
将在无限循环中从islice()
生成值。 len(source)
在q
元素之后切断迭代。但由于__iter__
重用,它仍然保持迭代状态。
如果你必须有一个专用的迭代器,坚持一个类对象并创建一个 iterable ,那么每次调用from itertools import cycle, islice
class CyclicSource:
def __init__(self, source):
self.length = len(source)
self.source = cycle(source)
def __iter__(self):
return islice(self.source, self.length)
时它都会返回一个新的迭代器:
cycle()
这使状态保持在islice()
迭代器中,但每次为此创建迭代器时,只需创建一个新的islice()
对象。它基本上封装了上面的{{1}}方法。