Search code examples
pythonkivy

Using kivy clock to wait before execution


I have a list which I have fetched from a server and here is what I want to do

result = [1,7,0,0,2,4....]

def got_json(req, result):
    for i in result:
        if i is odd:
            wait for 5 seconds and call my_function
            now continue
        else:
            call my_function (now)

So basically I'm looking for more like a time.sleep() but using time.sleep just freezes the app, I just want to pause the execution of my for loop in got_json method and not all other stuff which I suppose time.sleep does.

I tried using Clock.schedule_once using this code

class MyWidget(BoxLayout):

    def onbuttonclick(self):
        my_list = range(10)
        for i in my_list:
            if (i%2 == 0) :
                Clock.schedule_once(self.my_callback, 5)
                continue

            print("Whatever")

    def my_callback(self,dt):
        print("called")

The output seems like it is indeed scheduling the function but its not stopping the execution of the for loop, which is what I want Output of above code

Whatever
Whatever
Whatever
Whatever
Whatever
called
called
called
called
called

Output I want to have

Whatever
**5 seconds**

called
Whatever
**5 seconds**

called 

and so on...
How can I use the Clock object to do what I want? Thanks


Solution

  • This is interesting question. Using thread - is most universal solution for tasks like this in general. However, if we talking about this concrete case, you can use generator and it's yield points to return yourself control flow and resume execution later using Clock.schedule_once:

    from functools import wraps
    
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.clock import Clock
    
    
    def yield_to_sleep(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            gen = func()
            def next_step(*_):
                try:
                    t = next(gen)  # this executes 'func' before next yield and returns control to you
                except StopIteration:
                    pass
                else:
                    Clock.schedule_once(next_step, t)  # having control you can resume func execution after some time
            next_step()
        return wrapper
    
    
    @yield_to_sleep  # use this decorator to cast 'yield' to non-blocking sleep
    def test_function():
        for i in range(10):
            if (i % 2 == 0):
                yield 5  # use yield to "sleep"
                print('Called')
            else:
                print('Whatever')
    
    
    class TestApp(App):
        def build(self):
            test_function()
            return BoxLayout()
    
    
    if __name__ == '__main__':
        TestApp().run()
    

    Upd:

    def func():
        yield 'STEP 1'
        yield 'STEP 2'
    
    
    gen = func()
    print('Result of calling generator-function is generator object:', gen, '\n')
    
    
    res1 = next(gen)
    print('''Calling next() on generator object executes original 
    function to the moment of yield point and freeze it\'s state:\n''', res1, '\n')
    
    
    print('''Look at line of code that prints this mesage:
    It\'s not located inside func(), 
    but we were able to call it "in the middle" of executing func():
    We see msg "STEP 1", but we don't see "STEP 2" yet.
    This is what makes generators so cool: we can execute part of it,
    do anything we want and resume execution later.
    In "yield_to_sleep" this resuming delegated to Clock.schedule_once
    making generator being executed after some time and without freezing anything.''', '\n')
    
    
    res2 = next(gen)
    print('Another next() and we on next yield point inside func():\n', res2, '\n')
    
    
    try:
        next(gen)
    except StopIteration:
        print('''If no yield points left, another next() call will finish func()
    and raise StopIteration exception. It\'s not error it\'s just way
    to say to outer code that generator is done.''', '\n')