web-dev-qa-db-ja.com

Pythonでタイムアウトしたキーボード入力

どのように入力をユーザーに促しますが、N秒後にタイムアウトしますか?

Googleは http://mail.python.org/pipermail/python-list/2006-January/533215.html でそれに関するメールスレッドを指していますが、機能していないようです。タイムアウトが発生するステートメントは、sys.input.readlineかtimer.sleep()かに関係なく、常に取得されます。

<type 'exceptions.TypeError'>:[raw_] inputには最大1つの引数が必要で、2つ必要です

どういうわけか、例外はキャッチに失敗します。

43
pupeno

リンクした例は間違っており、ブロックを読み取るときではなく、アラームハンドラを呼び出すときに実際に例外が発生しています。これを試してみてください:

import signal
TIMEOUT = 5 # number of seconds your want for timeout

def interrupted(signum, frame):
    "called when read times out"
    print 'interrupted!'
signal.signal(signal.SIGALRM, interrupted)

def input():
    try:
            print 'You have 5 seconds to type in your stuff...'
            foo = raw_input()
            return foo
    except:
            # timeout
            return

# set alarm
signal.alarm(TIMEOUT)
s = input()
# disable the alarm after success
signal.alarm(0)
print 'You typed', s
30
user137673

選択呼び出しの使用は短く、はるかに移植性が高いはずです

import sys, select

print "You have ten seconds to answer!"

i, o, e = select.select( [sys.stdin], [], [], 10 )

if (i):
  print "You said", sys.stdin.readline().strip()
else:
  print "You said nothing!"
82
Pontus

Pythonソリューションではありませんが、...

CentOS(Linux)で実行しているスクリプトでこの問題に遭遇しましたが、私の状況でうまくいったのは、サブプロセスでBashの「read -t」コマンドを実行するだけでした。残忍な嫌なハックは知っていますが、それがどれほどうまく機能したかについては十分に罪悪感を覚えており、ここでみんなと共有したいと思いました。

import subprocess
subprocess.call('read -t 30', Shell=True)

必要なのは、ENTERキーが押されない限り30秒間待機するものだけでした。これはうまくいきました。

11
Locane

そして、これはWindows上で動作するものです

これらのサンプルをWindowsで動作させることができなかったため、StackOverflowのさまざまな回答をマージして、次の結果を得ました。


import threading, msvcrt
import sys

def readInput(caption, default, timeout = 5):
    class KeyboardThread(threading.Thread):
        def run(self):
            self.timedout = False
            self.input = ''
            while True:
                if msvcrt.kbhit():
                    chr = msvcrt.getche()
                    if ord(chr) == 13:
                        break
                    Elif ord(chr) >= 32:
                        self.input += chr
                if len(self.input) == 0 and self.timedout:
                    break    


    sys.stdout.write('%s(%s):'%(caption, default));
    result = default
    it = KeyboardThread()
    it.start()
    it.join(timeout)
    it.timedout = True
    if len(it.input) > 0:
        # wait for rest of input
        it.join()
        result = it.input
    print ''  # needed to move to next line
    return result

# and some examples of usage
ans = readInput('Please type a name', 'john') 
print 'The name is %s' % ans
ans = readInput('Please enter a number', 10 ) 
print 'The number is %s' % ans 
6
Paul

パウロの答えはうまくいきませんでした。私のために働く以下の修正されたコード

  • windows 7 x64

  • バニラCMDシェル(例:not git-bashまたはその他の非M $シェル)

    -表示されるgit-bashではmsvcrtが機能しません。

  • python 3.6

(ポールの回答を直接編集すると、python 2.x-> 3.xから変更されるため、編集には大きすぎるようです(py2はまだ使用する)

import sys, time, msvcrt

def readInput( caption, default, timeout = 5):

    start_time = time.time()
    sys.stdout.write('%s(%s):'%(caption, default))
    sys.stdout.flush()
    input = ''
    while True:
        if msvcrt.kbhit():
            byte_arr = msvcrt.getche()
            if ord(byte_arr) == 13: # enter_key
                break
            Elif ord(byte_arr) >= 32: #space_char
                input += "".join(map(chr,byte_arr))
        if len(input) == 0 and (time.time() - start_time) > timeout:
            print("timing out, using default value.")
            break

    print('')  # needed to move to next line
    if len(input) > 0:
        return input
    else:
        return default

# and some examples of usage
ans = readInput('Please type a name', 'john') 
print( 'The name is %s' % ans)
ans = readInput('Please enter a number', 10 ) 
print( 'The number is %s' % ans) 
5
mike

これに20分ほど費やしたので、ここに置くのは一見の価値があると思いました。ただし、user137673の答えから直接構築されています。私はこのようなことをするのが最も便利だと感じました:

#! /usr/bin/env python

import signal

timeout = None

def main():
    inp = stdinWait("You have 5 seconds to type text and press <Enter>... ", "[no text]", 5, "Aw man! You ran out of time!!")
    if not timeout:
        print "You entered", inp
    else:
        print "You didn't enter anything because I'm on a tight schedule!"

def stdinWait(text, default, time, timeoutDisplay = None, **kwargs):
    signal.signal(signal.SIGALRM, interrupt)
    signal.alarm(time) # sets timeout
    global timeout
    try:
        inp = raw_input(text)
        signal.alarm(0)
        timeout = False
    except (KeyboardInterrupt):
        printInterrupt = kwargs.get("printInterrupt", True)
        if printInterrupt:
            print "Keyboard interrupt"
        timeout = True # Do this so you don't mistakenly get input when there is none
        inp = default
    except:
        timeout = True
        if not timeoutDisplay is None:
            print timeoutDisplay
        signal.alarm(0)
        inp = default
    return inp

def interrupt(signum, frame):
    raise Exception("")

if __== "__main__":
    main()
3
dylnmc

次のコードは私のために働いた。

Raw_Inputを取得するスレッドと特定の時間待機するスレッドの2つのスレッドを使用しました。スレッドのいずれかが終了すると、両方のスレッドが終了して返されます。

def _input(msg, q):
    ra = raw_input(msg)
    if ra:
        q.put(ra)
    else:
        q.put("None")
    return

def _slp(tm, q):
    time.sleep(tm)
    q.put("Timeout")
    return

def wait_for_input(msg="Press Enter to continue", time=10):
    q = Queue.Queue()
    th = threading.Thread(target=_input, args=(msg, q,))
    tt = threading.Thread(target=_slp, args=(time, q,))

    th.start()
    tt.start()
    ret = None
    while True:
        ret = q.get()
        if ret:
            th._Thread__stop()
            tt._Thread__stop()
            return ret
    return ret

print time.ctime()    
t= wait_for_input()
print "\nResponse :",t 
print time.ctime()
3
Mechatron

Locane's for windowsに類似:

import subprocess  
subprocess.call('timeout /T 30')
2

ポータブルでシンプルなPython 3スレッドを使用したソリューションです。これは、クロスプラットフォームで動作する唯一のソリューションです。

私が試した他のすべてのものには問題がありました:

  • Signal.SIGALRMの使用:Windowsで機能しない
  • 選択呼び出しの使用:Windowsでは機能しません
  • (スレッドの代わりに)プロセスの強制終了を使用する:stdinは新しいプロセスでは使用できません(stdinは自動的に閉じられます)
  • StdinをStringIOにリダイレクトし、stdinに直接書き込む:input()が既に呼び出されている場合、以前のstdinに書き込みます( https://stackoverflow.com/a/15055639/9624704 を参照)
    from threading import Thread
    class myClass:
        _input = None

        def __init__(self):
            get_input_thread = Thread(target=self.get_input)
            get_input_thread.daemon = True  # Otherwise the thread won't be terminated when the main program terminates.
            get_input_thread.start()
            get_input_thread.join(timeout=20)

            if myClass._input is None:
                print("No input was given within 20 seconds")
            else:
                print("Input given was: {}".format(myClass._input))


        @classmethod
        def get_input(cls):
            cls._input = input("")
            return
1
jorisv92

この質問は重複したターゲットとして機能しているように見えるので、 here 重複した質問で受け入れられた回答へのリンク。

機能

  • プラットフォームに依存しない(Unix/Windows)。
  • StdLibのみ、外部依存関係なし。
  • スレッドのみ、サブプロセスなし。
  • タイムアウト時の即時割り込み。
  • タイムアウト時のプロンプターのクリーンシャットダウン。
  • 期間中に無制限の入力が可能です。
  • 簡単に拡張可能なPromptManagerクラス。
  • プログラムはタイムアウト後に再開する場合があり、プログラムを再起動せずにプロンプ​​ターインスタンスを複数回実行できます。
0
Darkonaut

私のクロスプラットフォームソリューション

def input_process(stdin_fd, sq, str):
    sys.stdin = os.fdopen(stdin_fd)
    try:
        inp = input (str)
        sq.put (True)
    except:
        sq.put (False)

def input_in_time (str, max_time_sec):
    sq = multiprocessing.Queue()
    p = multiprocessing.Process(target=input_process, args=( sys.stdin.fileno(), sq, str))
    p.start()
    t = time.time()
    inp = False
    while True:
        if not sq.empty():
            inp = sq.get()
            break
        if time.time() - t > max_time_sec:
            break
    p.terminate()
    sys.stdin = os.fdopen( sys.stdin.fileno() )
    return inp
0
iperov