pythonooprecursiongeneratorobject-design

How to create nested generator structures in python?


I'm trying to create an ImageSeries object, where I want to retrieve images in a certain pattern (for every value in xy, and every value in z), and I call methods that will append a generator to a tasks list, and run the generator through two for loops to do this.

But my second task gets exhausted after the first iteration of the first task, which is not my desired outcome. I want the second task to run every iteration of the first task.

I was wondering if there are efficient ways to program patterns like this.

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])

Current output:

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

Expected output:

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

Solution

  • Here you go

    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()
    

    Did some things:

    1. called run()
    2. self.tasks made no sense as a list, becuase each cell has a different meaning, so I separated it into two separate member variables.
    3. The main thing was making sure the generator would be created again on each run, as it cannot be reset. I accomplished that by using a lambda, so you can call a function that creates the generator every time, instead of the generator itself. Notice the self._xy_tasks(). This calls a function that creates the generator.