Функция signal.alarm, на которой основано рекомендуемое @jer решение, к сожалению, работает только под Unix. Если вам нужно кроссплатформенное или Windows-специфичное решение, вы можете основать его на threading.Timer, используя thread.interrupt_main для отправки KeyboardInterrupt
в основной поток из потока таймера. Т.е. :
import thread
import threading
def raw_input_with_timeout(prompt, timeout=30.0):
print prompt,
timer = threading.Timer(timeout, thread.interrupt_main)
astring = None
try:
timer.start()
astring = raw_input(prompt)
except KeyboardInterrupt:
pass
timer.cancel()
return astring
это вернет None независимо от того, истекут ли 30 секунд или пользователь явно решит нажать control-C, чтобы отказаться от ввода чего-либо, но, кажется, нормально рассматривать эти два случая одинаково (если вам нужно различать, вы можете использовать для таймера собственную функцию, которая, перед прерыванием основного потока записывает где-то факт того, что таймаут произошел, и в своем обработчике для KeyboardInterrupt
обращаться к этому "где-то", чтобы отличить, какой из двух случаев произошел).
Edit: Я мог бы поклясться, что это работает, но, должно быть, я ошибся - в приведенном выше коде отсутствует явно необходимый timer.start()
, и даже с ним я не могу заставить его работать дальше. select.select
был бы очевидной другой вещью, чтобы попробовать, но он не будет работать на "нормальном файле" (включая stdin) в Windows - в Unix он работает на всех файлах, в Windows только на сокетах.
Так что я не знаю, как сделать кроссплатформенный "raw input with timeout". Специфичный для windows можно построить с помощью узкого цикла, опрашивающего msvcrt.kbhit, выполняющего msvcrt.getche
(и проверяющего, не является ли это возвратом, указывающим на завершение вывода, в этом случае он выходит из цикла, иначе накапливается и продолжает ждать) и проверяющего время тайм-аута, если это необходимо. Я не могу проверить, потому что у меня нет машины с Windows (все они Mac и Linux), но вот непроверенный код, который я бы предложил:
import msvcrt
import time
def raw_input_with_timeout(prompt, timeout=30.0):
print prompt,
finishat = time.time() + timeout
result = []
while True:
if msvcrt.kbhit():
result.append(msvcrt.getche())
if result[-1] == '\r': # or \n, whatever Win returns;-)
return ''.join(result)
time.sleep(0.1) # just to yield to other processes/threads
else:
if time.time() > finishat:
return None
ОП в комментарии говорит, что не хочет возвращать None
по таймауту, но какова альтернатива? Вызвать исключение? Возвращение другого значения по умолчанию? Какую бы альтернативу он ни хотел, он явно может поставить ее вместо моего return None
;-).
Если вы не хотите делать тайм-аут только потому, что пользователь печатает медленно (в отличие от того, что не печатает вообще!), вы можете пересчитывать finishat после каждого успешного ввода символа.
Я нашел решение этой проблемы в сообщении блога . Вот код из этого сообщения в блоге:
import signal
class AlarmException(Exception):
pass
def alarmHandler(signum, frame):
raise AlarmException
def nonBlockingRawInput(prompt='', timeout=20):
signal.signal(signal.SIGALRM, alarmHandler)
signal.alarm(timeout)
try:
text = raw_input(prompt)
signal.alarm(0)
return text
except AlarmException:
print '\nPrompt timeout. Continuing...'
signal.signal(signal.SIGALRM, signal.SIG_IGN)
return ''
Обратите внимание: этот код будет работать только в ОС * nix .