13

我正在尝试创建一个循环 python 函数,该函数执行任务并提示用户响应,如果用户在给定时间内没有响应,则序列将重复。

这大致基于这个问题:How to set time limit on raw_input

任务由 表示some_function()。超时是以秒为单位的变量。以下代码有两个问题:

  1. 无论用户是否提示,raw_input 提示在指定的 4 秒时间后都不会超时。

  2. 当输入 'q' 的 raw_input 时(没有 '' 因为我知道输入的任何内容都会自动输入为字符串),该函数不会退出循环。

`

import thread
import threading
from time import sleep

def raw_input_with_timeout():
    prompt = "Hello is it me you're looking for?"
    timeout = 4
    astring = None
    some_function()
    timer = threading.Timer(timeout, thread.interrupt_main)
    try:
        timer.start()
        astring = raw_input(prompt)
    except KeyboardInterrupt:
        pass
    timer.cancel()
    if astring.lower() != 'q':
        raw_input_with_timeout()
    else:
        print "goodbye"

`

4

6 回答 6

3

警告:这旨在按要求在 *nix 和 OSX 中工作,但绝对不能在 Windows 中工作。

我使用ActiveState 配方的这种修改作为下面代码的基础。这是一个易于使用的对象,可以读取超时输入。它使用轮询一次收集一个字符并模拟raw_input()/的行为input()

超时输入

注意:显然_getch_nix()下面的方法不适用于 OP,但它适用于我在 OSX 10.9.5 上。虽然它似乎只在 32 位 python 中工作,但你可能会幸运地调用_getch_osx()它,因为 Carbon 不完全支持 64 位。

import sys
import time


class TimeoutInput(object):
    def __init__(self, poll_period=0.05):
        import sys, tty, termios  # apparently timing of import is important if using an IDE
        self.poll_period = poll_period

    def _getch_nix(self):
        import sys, tty, termios
        from select import select
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            [i, o, e] = select([sys.stdin.fileno()], [], [], self.poll_period)
            if i:
                ch = sys.stdin.read(1)
            else:
                ch = ''
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch

    def _getch_osx(self):
        # from same discussion on the original ActiveState recipe:
        # http://code.activestate.com/recipes/134892-getch-like-unbuffered-character-reading-from-stdin/#c2
        import Carbon
        if Carbon.Evt.EventAvail(0x0008)[0] == 0:  # 0x0008 is the keyDownMask
            return ''
        else:
            # The event contains the following info:
            # (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            #
            # The message (msg) contains the ASCII char which is
            # extracted with the 0x000000FF charCodeMask; this
            # number is converted to an ASCII character with chr() and
            # returned
            (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            return chr(msg & 0x000000FF)

    def input(self, prompt=None, timeout=None,
              extend_timeout_with_input=True, require_enter_to_confirm=True):
        """timeout: float seconds or None (blocking)"""
        prompt = prompt or ''
        sys.stdout.write(prompt)  # this avoids a couple of problems with printing
        sys.stdout.flush()  # make sure prompt appears before we start waiting for input
        input_chars = []
        start_time = time.time()
        received_enter = False
        while (time.time() - start_time) < timeout:
            # keep polling for characters
            c = self._getch_osx()  # self.poll_period determines spin speed
            if c in ('\n', '\r'):
                received_enter = True
                break
            elif c:
                input_chars.append(c)
                sys.stdout.write(c)
                sys.stdout.flush()
                if extend_timeout_with_input:
                    start_time = time.time()
        sys.stdout.write('\n')  # just for consistency with other "prints"
        sys.stdout.flush()
        captured_string = ''.join(input_chars)
        if require_enter_to_confirm:
            return_string = captured_string if received_enter else ''
        else:
            return_string = captured_string
        return return_string

测试一下

# this should work like raw_input() except it will time out
ti = TimeoutInput(poll_period=0.05)
s = ti.input(prompt='wait for timeout:', timeout=5.0,
             extend_timeout_with_input=False, require_enter_to_confirm=False)
print(s)

重复输入

据我了解,这实现了您的初衷。我认为进行递归调用没有任何价值-我认为您想要的只是重复输入?如果那是错误的,请纠正我。

ti = TimeoutInput()
prompt = "Hello is it me you're looking for?"
timeout = 4.0
while True:
    # some_function()
    s = ti.input(prompt, timeout)
    if s.lower() == 'q':
        print "goodbye"
        break
于 2015-08-31T14:43:12.627 回答
1

您可以在输入之前设置警报,然后将警报绑定到自定义处理程序。在给定期间警报响起后,处理程序引发异常,您的自定义input函数可能会处理其余部分。
一个简单的例子:

import signal
class InputTimedOut(Exception):
    pass

def inputTimeOutHandler(signum, frame):
    "called when read times out"
    print 'interrupted!'
    raise InputTimedOut

signal.signal(signal.SIGALRM, inputTimeOutHandler)

def input_with_timeout(timeout=0):
    foo = ""
    try:
            print 'You have {0} seconds to type in your stuff...'.format(timeout)
            signal.alarm(timeout)
            foo = raw_input()
            signal.alarm(0)    #disable alarm
    except InputTimedOut:
            pass
    return foo

s = input_with_timeout(timeout=3)
print 'You typed', s

应得的信用:Python中超时的键盘输入

于 2015-09-03T23:11:17.273 回答
0

另一种方法是将 IO 阻塞放置在新线程中(与您在主线程中使用它的建议方案相反)。对此的警告是,在 python 中没有一种干净的方式来杀死一个线程,所以这对于重复调用并不好(N 个线程会一直挂到 main 结束,我认为 raw_input 不会很好......) .

所以,请注意,这一次有效,远非完美的解决方案

import threading
import Queue

def threaded_raw_input(ret_queue):
    print("In thread")
    prompt = "Hello is it me you're looking for?"
    astring = raw_input(prompt)
    ret_queue.put(astring)

if __name__ == '__main__':
    print("Main")
    ret_queue = Queue.Queue()
    th = threading.Thread(target=threaded_raw_input, args=(ret_queue,))
    th.daemon = True    
    th.start()
    try:
        astring = ret_queue.get(timeout=4)
    except Queue.Empty:
        print("\nToo late")
    else:
        print("Your input {}".format(astring))
于 2015-09-03T05:59:51.583 回答
0

这只是概念教授。要求用户输入数据。

import time, os
import curses

def main(win):
    win.nodelay(True)
    x=0
    output=""
    while 1:
        win.clear()
        win.addstr(str("Prompt:"))
        win.addstr(str(output))
        x+=1
        try:
           key = win.getkey()
           if key == os.linesep:
              return output
           output += str(key)
           x = 0             
        except: 
           pass
        if x>=50:  # 5s
           return output
        time.sleep(0.1) 

curses.wrapper(main)
于 2015-09-03T22:01:58.733 回答
0

我不认为有一种方法可以显示一个提示,该提示将在时间过去后过期而不显示来自另一个线程的不同消息。

您可以在调用 raw_input 之前添加以下行:

 thread.start_new_thread(interrupt_user,())

您可以interrupt_user按如下方式定义函数:

sleep(5)
print "\nTime up"

raw_input_with_time函数中,不要调用 sleep。相反,将调用前的时间保存到 raw_input,并确定调用后经过的时间是否超过 5 秒。此外,如果用户输入了“q”,那么它不应该调用自己,因此循环将停止。

于 2015-08-24T23:47:25.547 回答
0

如果不是some_function在输入超时时调用,而是将转换为以超时间隔持续运行的后台线程,该怎么办?当主线程在等待输入时被永久阻塞时,工作将继续进行。您可能会决定根据工人正在做什么(工作或睡觉)对该输入做出不同的反应 - 您可能会完全忽略它。AFAIK,不接受输入或接受输入但忽略它之间没有明显的区别。这个想法利用了这一点。

注意:我打算做的只是展示另一种思考问题的方式,这种方式可能适合也可能不适合您的特定情况。我确实认为它非常灵活。

概念证明:

from __future__ import print_function
from threading import Event, Thread
from time import sleep

def some_function():
    print("Running some function")
    sleep(1)

def raw_input_with_timeout():
    cancel_event = Event()
    wip_event = Event() # Only needed to know if working or waiting

    def worker():
        timeout = 4
        try:
            while not cancel_event.is_set():
                wip_event.set()
                some_function()
                print("Repeating unless 'q' is entered within %d secs!" % timeout)
                wip_event.clear()
                cancel_event.wait(timeout)
        finally:
            wip_event.clear()

    worker_thread = Thread(target=worker)
    worker_thread.start()
    try:
        while not cancel_event.is_set():
            try:
                if raw_input() == 'q' and not wip_event.is_set():
                    cancel_event.set()
            except KeyboardInterrupt:
                pass
    finally:
        cancel_event.set()
        worker_thread.join()
    print("Goodbye")

它不依赖于任何特定于平台的东西;这只是简单的 Python 代码。只有在尝试了一些从线程内获取输入的替代实现之后,我才意识到将用户输入留给主线程有多大的优势。

我没有太注意让它安全和干净,但可以肯定的是,它可以在保持整体结构的同时完成。我能看到的最大缺陷是早期的输入永远不会消失。当工作人员输出时,它会导致混乱,从而掩盖早期的输入。如果您按q但未及时按,则即使在屏幕上这些 s 不相邻Enter时,按qand Enternext time 也会导致输入。通常这是命令行应用程序的工作方式,所以我不确定它是否值得修复。您也可以考虑接受仅由s 组成的输入作为取消。另一种选择是直接读取,而不是使用.qqqqstdinraw_input

使代码结构更好的一些想法是使主线程更加笨拙,并将所有输入传递给工作线程(使用队列)以决定要做什么。

于 2015-09-04T00:26:22.647 回答