Search code examples
pythonqtpyside6

Iterate with an interval timer


I am trying to create a function which accepts a list of items and iterates through them with a specified time interval (using QTimer from the Qt framework), without disrupting the rest of the program flow.

I've managed to get this working with the following code which uses an inner function, but I'm curious if this could be done in a more elegant way, without using the self.index instance variable. Ideally this counter should be encapsulated within the iterate_with_interval method, but I couldn't find a way to increment it without using global.

I suppose this could be done with a dedicated class for this functionality but I'm wondering if it can be done without one.

from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QWidget, QApplication


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.index = 0
        self.iterate_with_interval(['a', 'b', 'c', 'd', 'e'], 200)
        print('Execution continues')

    def iterate_with_interval(self, items, interval):
        timer = QTimer()
        timer.setInterval(interval)

        def iterate(items, timer):
            if self.index < len(items):
                print(items[self.index])
                self.index += 1
            else:
                timer.stop()

        timer.timeout.connect(lambda: iterate(items, timer))
        timer.start()


if __name__ == '__main__':
    app = QApplication()
    window = MainWindow()
    window.show()
    app.exec()

Solution

  • You will always need an intermediate function that obtains the value of the list, so the most elegant solution is to create a class that handles the logic through an iterator.

    from PySide6.QtCore import QTimer, Signal
    from PySide6.QtWidgets import QWidget, QApplication
    
    
    class TimerIterator(QTimer):
        value_changed = Signal(object)
    
        def __init__(self, values=None, parent=None):
            super().__init__(parent)
            self._values = []
            self.timeout.connect(self.handle_timeout)
            
            self.values = values or []
    
        @property
        def values(self):
            return self._values
    
        @values.setter
        def values(self, values):
            self._values = values
            self.setProperty("iterator", iter(self.values))
    
        def handle_timeout(self):
            try:
                value = next(self.property("iterator"))
            except StopIteration:
                self.stop()
            else:
                self.value_changed.emit(value)
    
    
    class MainWindow(QWidget):
        def __init__(self):
            super().__init__()
    
            self.timer_iterator = TimerIterator()
            self.timer_iterator.values = ["a", "b", "c", "d", "e"]
            self.timer_iterator.setInterval(200)
            self.timer_iterator.value_changed.connect(print)
            self.timer_iterator.start()
    
    
    if __name__ == "__main__":
        app = QApplication()
        window = MainWindow()
        window.show()
        app.exec()