如果您的所有对象(活动和非活动)的集合永远不会改变,特别是如果活动和非活动状态之间的转换很常见,那么对象的总数不会太离谱,或者非活动对象的集合通常不会覆盖大部分在整个集合中,cycle 在这里仍然可以很好地工作,方法是保持 set 周围的非活动对象并过滤掉当前非活动对象“活动”:
from itertools import cycle, filterfalse
allobjects = [...]
numuniqueobjects = len(set(allobjects))
inactiveobjects = set()
# Each time we request an item, filterfalse pulls items from the cycle
# until we find one that isn't in our inactive set
for object in filterfalse(inactiveobjects.__contains__, cycle(allobjects)):
# ... do actual stuff with object ...
# Any objects that should go active again get removed from the set and will be
# seen again the next time their turn comes up in the original order
inactiveobjects -= objects_that_should_become_active()
# Won't see this object again until it's removed from inactiveobjects
if object.should_go_inactive():
inactiveobjects.add(object)
if len(inactiveobjects) == numuniqueobjects:
# Nothing is active, continuing loop would cause infinite loop
break
这种设计的优点是:
- 激活和停用对象的成本很低
- 它无限期地保留原始迭代顺序(如果一个对象最初位于位置 4,当它变为非活动状态时将被跳过,当它再次变为活动状态时,它会在下一次循环时重新出现在位置 3 之后和位置 5 之前)
主要的缺点是它在“什么都没有改变”的情况下增加了一点点开销,尤其是当inactiveobjects 的set 增长到对象总数的相当一部分时;即使过滤掉许多个对象,你仍然需要cycle 所有个对象。
如果这不适合您的用例,wim 建议的基于deque 的自定义版本的cycle 可能是最好的通用解决方案:
from collections import deque
from collections.abc import Iterator
class mutablecycle(Iterator):
def __init__(self, it):
self.objects = deque(it)
self.objects.reverse() # rotate defaults to equivalent of appendleft(pop())
# reverse so next item always at index -1
def __next__(self):
self.objects.rotate() # Moves rightmost element to index 0 efficiently
try:
return self.objects[0]
except IndexError:
raise StopIteration
def removecurrent(self):
# Remove last yielded element
del self.objects[0]
def remove(self, obj):
self.objects.remove(obj)
def add(self, obj, *, tofront=True):
if tofront:
# Putting it on right makes it be yielded on next request
self.objects.append(obj)
else:
# Putting it on left makes it appear after all other elements
self.objects.appendleft(obj)
用途是:
mycycle = mutablecycle(allobjects):
for object in mycycle:
# ... do stuff with object ...
if object.should_go_inactive():
mycycle.removecurrent() # Implicitly removes object currently being iterated