【问题标题】:Postponing functions in pythonpython中的延迟函数
【发布时间】:2011-07-07 20:09:07
【问题描述】:

在 JavaScript 中,我习惯于调用函数稍后执行,像这样

function foo() {
    alert('bar');
}

setTimeout(foo, 1000);

这不会阻止其他代码的执行。

我不知道如何在 Python 中实现类似的功能。我可以使用睡眠

import time
def foo():
    print('bar')

time.sleep(1)
foo()

但这会阻止其他代码的执行。 (实际上,在我的情况下,阻塞 Python 本身并不是问题,但我无法对该方法进行单元测试。)

我知道线程是为不同步执行而设计的,但我想知道是否存在类似于setTimeoutsetInterval 的更简单的方法。

【问题讨论】:

    标签: python multithreading setinterval


    【解决方案1】:

    您需要来自threading 模块的Timer 对象。

    from threading import Timer
    from time import sleep
    
    def foo():
        print "timer went off!"
    t = Timer(4, foo)
    t.start()
    for i in range(11):
        print i
        sleep(.5)
    

    如果你想重复,这里有一个简单的解决方案:不要使用Timer,而是使用Thread,但传递一个类似这样的函数:

    def call_delay(delay, repetitions, func, *args, **kwargs):             
        for i in range(repetitions):    
            sleep(delay)
            func(*args, *kwargs)
    

    这不会执行无限循环,因为如果操作不当,可能会导致线程不会死掉以及其他不愉快的行为。更复杂的方法可能使用基于Event 的方法like this one

    【讨论】:

    • 让我请求一个变化。如果我想像setInterval 那样每 x 秒重复一次调用怎么办?我可以让函数在最后启动自己的计时器,但这会产生无限多的线程吗?
    • @Andrea,是的,这不是一个好方法。抱歉耽搁了;到了就寝时间。第一种方法见上文。
    • 谢谢。事实上,在询问后不久,我就开始为这种行为制作装饰器。唯一的问题是如何阻止它永远运行,由于这个问题的输入,我解决了这个问题。 stackoverflow.com/questions/5179467/…我终于用装饰器的工作版本更新了这个问题:-)
    • @Andrea,啊,是的,我打算建议使用Event 来终止循环。漂亮的装饰器。
    • 基于事件的方法不需要很复杂,例如call_repeatedly(interval, func, *args)
    【解决方案2】:

    JavaScript 可以做到这一点,因为它在事件循环中运行。这可以在 Python 中通过使用 Twisted 等事件循环或通过 GLib 或 Qt 等工具包在 Python 中完成。

    【讨论】:

      【解决方案3】:

      像 Javascript 的 setTimeout 这样的异步回调需要事件驱动的架构。

      像流行的 twisted 这样的 Python 异步框架有 CallLater 可以满足您的需求,但这意味着在您的应用程序中采用事件驱动架构。

      另一种选择是使用线程并在线程中休眠。 Python 提供了一个 timer 来简化 等待 部分。但是,当您的线程唤醒并执行您的函数时,它位于一个单独的线程中,并且必须以线程安全的方式执行任何操作。

      【讨论】:

        【解决方案4】:

        问题是您的普通 python 脚本不能在框架中运行。该脚本被调用并控制主循环。使用 JavaScript,在您的页面上运行的所有脚本都在一个框架中运行,并且当超时过去时,该框架会调用您的方法。

        我自己没有使用过 pyQt(仅 C++ Qt),但您可以使用 startTimer() 在任何 QObject 上设置计时器。当计时器过去时,将调用您的方法的回调。您还可以使用 QTimer 并将超时信号连接到任意插槽。这是可能的,因为 Qt 运行一个事件循环,可以在稍后阶段调用您的方法。

        【讨论】:

        • 感谢您的回答,但将我的代码切换为仅用于此方法的 Qt 应用程序是不可行的。
        【解决方案5】:

        要在延迟后执行函数或使用事件循环(无线程)在给定秒数内重复函数,您可以:

        Tkinter

        #!/usr/bin/env python
        from Tkinter import Tk
        
        def foo():
            print("timer went off!")
        
        def countdown(n, bps, root):
            if n == 0:
                root.destroy() # exit mainloop
            else:
                print(n)
                root.after(1000 / bps, countdown, n - 1, bps, root)  # repeat the call
        
        root = Tk()
        root.withdraw() # don't show the GUI window
        root.after(4000, foo) # call foo() in 4 seconds
        root.after(0, countdown, 10, 2, root)  # show that we are alive
        root.mainloop()
        print("done")
        

        输出

        10
        9
        8
        7
        6
        5
        4
        3
        timer went off!
        2
        1
        done
        

        Gtk

        #!/usr/bin/env python
        from gi.repository import GObject, Gtk
        
        def foo():
            print("timer went off!")
        
        def countdown(n): # note: a closure could have been used here instead
            if n[0] == 0:
                Gtk.main_quit() # exit mainloop
            else:
                print(n[0])
                n[0] -= 1
                return True # repeat the call
        
        GObject.timeout_add(4000, foo) # call foo() in 4 seconds
        GObject.timeout_add(500, countdown, [10])
        Gtk.main()
        print("done")
        

        输出

        10
        9
        8
        7
        6
        5
        4
        timer went off!
        3
        2
        1
        done
        

        扭曲

        #!/usr/bin/env python
        from twisted.internet import reactor
        from twisted.internet.task import LoopingCall
        
        def foo():
            print("timer went off!")
        
        def countdown(n):
            if n[0] == 0:
                reactor.stop() # exit mainloop
            else:
                print(n[0])
                n[0] -= 1
        
        reactor.callLater(4, foo) # call foo() in 4 seconds
        LoopingCall(countdown, [10]).start(.5)  # repeat the call in .5 seconds
        reactor.run()
        print("done")
        

        输出

        10
        9
        8
        7
        6
        5
        4
        3
        timer went off!
        2
        1
        done
        

        异步

        Python 3.4 为异步 IO 引入了新的 provisional API -- asyncio module

        #!/usr/bin/env python3.4
        import asyncio
        
        def foo():
            print("timer went off!")
        
        def countdown(n):
            if n[0] == 0:
                loop.stop() # end loop.run_forever()
            else:
                print(n[0])
                n[0] -= 1
        
        def frange(start=0, stop=None, step=1):
            while stop is None or start < stop:
                yield start
                start += step #NOTE: loss of precision over time
        
        def call_every(loop, seconds, func, *args, now=True):
            def repeat(now=True, times=frange(loop.time() + seconds, None, seconds)):
                if now:
                    func(*args)
                loop.call_at(next(times), repeat)
            repeat(now=now)
        
        loop = asyncio.get_event_loop()
        loop.call_later(4, foo) # call foo() in 4 seconds
        call_every(loop, 0.5, countdown, [10]) # repeat the call every .5 seconds
        loop.run_forever()
        loop.close()
        print("done")
        

        输出

        10
        9
        8
        7
        6
        5
        4
        3
        timer went off!
        2
        1
        done
        

        注意:这些方法之间的界面和行为略有不同。

        【讨论】:

        • 我一直在寻找 Twisted 的 LoopingCall 的 aysncio 版本。感谢所有示例!
        【解决方案6】:

        抱歉,我不能发布超过 2 个链接,所以有关详细信息,请查看 PEP 380,最重要的是 asyncio

        asyncio 是此类问题的首选解决方案,除非您坚持使用线程或多处理。它是由 GvR 设计和实现的,名称为“Tulip”。它已由 GvR 在PyCon 2013 上引入,旨在成为一个事件循环来规则(和标准化)所有事件循环(如扭曲、gevent 等中的事件循环)并使它们相互兼容。之前已经提到过 asyncio,但 asyncio 的真正威力在于 yield from

        # asyncio is in standard lib for latest python releases (since 3.3)
        import asyncio
        
        # there's only one event loop, let's fetch that
        loop = asyncio.get_event_loop()
        
        # this is a simple reminder that we're dealing with a coro
        @asyncio.coroutine
        def f():
            for x in range(10):
                print(x)
                # we return with a coroutine-object from the function, 
                # saving the state of the execution to return to this point later
                # in this case it's a special sleep
                yield from asyncio.sleep(3)
        
        # one of a few ways to insert one-off function calls into the event loop
        loop.call_later(10, print, "ding!")
        # we insert the above function to run until the coro-object from f is exhausted and 
        # raises a StopIteration (which happens when the function would return normally)
        # this also stops the loop and cleans up - keep in mind, it's not DEAD but can be restarted
        loop.run_until_complete(f())
        # this closes the loop - now it's DEAD
        loop.close()
        

        =================

        >>> 
        0
        1
        2
        3
        ding!
        4
        5
        6
        7
        8
        9
        >>>
        

        【讨论】:

        • 能否请您添加更多关于您的答案的详细信息?
        • 当然,有什么特别要我补充的吗?我会评论这些行,也许可以阐明它是如何工作的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-05
        • 1970-01-01
        • 1970-01-01
        • 2012-12-08
        • 1970-01-01
        • 2016-03-22
        相关资源
        最近更新 更多