Если ваш набор всех объектов (активных и неактивных) никогда не меняется, и особенно если переходы между активным и неактивным состоянием являются общими, общее количество объектов не является возмутительным или набор неактивных объектов обычно не охватывает большинствоиз общего набора, 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 в следующий раз, когда вы выполните цикл)
Основным недостатком является то, что он добавляет чуть больше накладных расходов к «ничего не меняется»случай, особенно если set
из inactiveobjects
вырастает до заметной доли от общего числа оbjects;вам все равно придется cycle
всех объектов, даже если отфильтровать многих из них.
Если это не подходит для вашего варианта использования, пользовательская версия cycle
, построенный на deque
в соответствии с предложением wim , вероятно, является лучшим решением общего назначения:
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