Как создать вложенные структуры генератора в Python?

Я пытаюсь создать ImageSeries объект, где я хочу получить изображения по определенному шаблону (для каждого значения в xy и каждого значения в z), и я вызываю методы, которые добавят генератор в список задач, и запускаю генератор через два цикла for, чтобы выполнить этот.

Но моя вторая задача исчерпывается после первой итерации первой задачи, что не является моим желаемым результатом. Я хочу, чтобы вторая задача выполнялась на каждой итерации первой задачи.

Мне было интересно, есть ли эффективные способы программирования подобных шаблонов.

class ImageSeries:
    tasks = []

    def xy(self, position):
        print(position)
        yield "xy"

    def z(self, position):
        print(position)
        yield "z"

    def xy_scan(self, positions):
        self.tasks.append((self.xy(pos) for pos in positions))

    def z_scan(self, positions):
        self.tasks.append((self.z(pos) for pos in positions))

    def run(self):
        for i in self.tasks[0]:
            next(i)
            for j in self.tasks[1]:
                next(j)

    def __repr__(self):
        return str(self.tasks)
    

if __name__ == "__main__":
    s = ImageSeries()
    positions = [[0, 0], [100, 100], [1000, 1000]]
    s.xy_scan(positions)
    s.z_scan([0, 100, 1000, 10000])

Токовый выход:

[0, 0]
0
100
1000
10000
[100, 100]
[1000, 1000]

Ожидаемый результат:

>>> s.run()
[0, 0]
0
100
1000
10000
[100, 100]
0
100
1000
10000
[1000, 1000]
0
100
1000
10000

2 ответа

Решение

Ну вот

class ImageSeries:
    def __init__(self):
        self._xy_tasks = None
        self._z_tasks = None

    def xy(self, position):
        print(position)
        yield "xy"

    def z(self, position):
        print(position)
        yield "z"

    def xy_scan(self, positions):
        self._xy_tasks = lambda: (self.xy(pos) for pos in positions)

    def z_scan(self, positions):
        self._z_tasks = lambda: (self.z(pos) for pos in positions)

    def run(self):
        for xy_generator in self._xy_tasks():
            next(xy_generator)
            for z_generator in self._z_tasks():
                next(z_generator)

    def __repr__(self):
        return str(self._xy_tasks()) + " " + str(self._z_tasks())


if __name__ == "__main__":
    s = ImageSeries()
    positions = [[0, 0], [100, 100], [1000, 1000]]
    s.xy_scan(positions)
    s.z_scan([0, 100, 1000, 10000])
    s.run()

Сделал кое-что:

  1. называется run()
  2. В качестве списка self.tasks не имеет смысла, поскольку каждая ячейка имеет разное значение, поэтому я разделил его на две отдельные переменные-члены.
  3. Главное было убедиться, что генератор будет создаваться заново при каждом запуске, так как его нельзя сбросить. Я добился этого с помощью лямбда, поэтому вы можете вызывать функцию, которая каждый раз создает генератор, а не сам генератор. Обратите внимание наself._xy_tasks(). Это вызывает функцию, которая создает генератор.

Генераторы не знают, что они вложены. После того, как в первый раз генератор был исчерпан, он закончился. Фактически, вам не нужны генераторы в этом случае, поскольку они имеют смысл, когда вы повторяете длинный список, который не хотите хранить в памяти. Но здесь все повторяющиеся последовательности нужно сохранять в памяти. Вы можете использовать генератор только на верхнем уровне цикла. Но это имеет смысл только в том случае, если оно действительно длинное и получено из какого-то ручья. Если он уже находится в памяти, вам действительно не нужны генераторы. Все, что вы хотите, можно сделать намного проще

xy_list = [[0, 0], [100, 100], [1000, 1000]]
z_list = [0, 100, 1000, 10000]
for xy in xy_list:
    print(xy)
    for z in z_list:
        print(z)

Если вам нужен класс, просто используйте xy_scan, z_scan сохранить в self.xy_list, self.z_list и использовать то же самое for зациклиться run метод (просто добавьте self. к xy_list а также z_list)

Другие вопросы по тегам