6

Pythonでコンソールのキーボードイベントを処理したい。実行中のスクリプトには永続的な出力ストリームがいくつかあります。管理者がキープレス イベントをトリガーすると、スクリプトは出力内容を変更します。

私は次のようなコードでそれを行いました(「q」を押すと出力変更がトリガーされます)が、2つの問題があります

  1. 私の出力には増加したスペースがあります。デバッグ後、コード「tty.setraw(fd)」が原因であることがわかりましたが、解決方法がわかりません
  2. ctrl+c が機能しなくなった (# "tty.setraw(fd)" の場合、ctrl+c が機能する)

複雑すぎる場合、他のモジュールで私が望むことを実行できますか? curse モジュールを試してみましたが、ウィンドウ出力がフリーズし、マルチスレッドで調整できなかったようです

#!/usr/bin/python
import sys
import select
import tty, termios
import threading
import time

def loop():

    while loop_bool:
        if switch:   
            output = 'aaaa'
        else:
            output = 'bbbb'
        print output
        time.sleep(0.2)


def change():
    global switch
    global loop_bool    
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)

    try: 
        while loop_bool:
            tty.setraw(fd)
            i,o,e = select.select([sys.stdin],[],[],1)
            if len(i)!=0:
                if i[0] == sys.stdin:
                    input = sys.stdin.read(1)

                    if input =='q':
                        if switch:
                            switch = False                                         
                        else: 
                            switch =  True


        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    except KeyboardInterrupt:

        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        loop_bool = False


try:   
    switch = True
    loop_bool = True    
    t1=threading.Thread(target=loop)
    t2=threading.Thread(target=change)        

    t1.start()
    t2.start()

    t1.join(1)
    t2.join(1)
except KeyboardInterrupt:

    loop_bool = False
4

1 に答える 1

2

これはおそらく、使用しているプラ​​ットフォームや、使用しているターミナル エミュレータによって異なります。問題が解決するかどうかはわかりませんが…</p>

tty.setraw「標準モード」をオフに設定するだけで、を呼び出さずに文字ごとの入力を取得できるはずです。これICANONは、 の lflag 属性のビットをマスクすることによって行いますtcgetattr()。VMIN または VTIME 属性も設定する必要がある場合がありますが、デフォルトはすでに正しいはずです。

詳細については、Linux のマニュアル ページの「正規モードと非正規モード」、 OS X のマニュアル ページの「非正規モード入力処理」、または別のプラットフォームを使用している場合は同等のセクションを参照してください。

明示的なクリーンアップを行うよりも、これをコンテキスト マネージャーとして記述した方がおそらくクリーンです。特に、既存のコードはsetraw毎回ループを実行し、最後にのみ復元するためです。それらは理想的には一致するペアである必要があり、withステートメントを使用するとそれが保証されます。(また、この方法では、句と通常の流れで自分自身を繰り返す必要はありませんexcept。)

@contextlib.contextmanager
def decanonize(fd):
    old_settings = termios.tcgetattr(fd)
    new_settings = old_settings[:]
    new_settings[3] &= ~termios.ICANON
    termios.tcsetattr(fd, termios.TCSAFLUSH, new_settings)
    yield
    termios.tcsetattr(fd, termios.TCSAFLUSH, old_settings)

今:

def change():
    global switch
    global loop_bool

    with decanonize(sys.stdin.fileno()):
        try:
            while loop_bool:
                i,o,e = select.select([sys.stdin],[],[],1)
                if i and i[0] == sys.stdin:
                    input = sys.stdin.read(1)                    
                    if input =='q':
                        switch = not switch
        except KeyboardInterrupt:
            loop_bool = False

または、withブロックをより低いレベル ( 内while、または少なくとも内try) に配置する必要があるかもしれません。

(追伸、ネストのいくつかのレベルを削除するために、コードの数行を同等であるがより単純な形式に変換しました。)

YMMV ですが、これが私の Mac でのテストです。

Retina:test abarnert$ python termtest.py 
aaaa
aaaa
aaaa
qbbbb
bbbb
bbbb
qaaaa
aaaa
aaaa
^CRetina:test abarnert$

これは、任意の termios フラグを一時的に設定またはクリアするnew_settings[3] &= ~termios.ECHOために、関数をより一般的なものに置き換えたいと思われることを意味します。decanonize(また、 a の代わりに a がtcgetattr返されるとよいので、 の代わりに行うか、少なくとも属性インデックスの記号定数を指定できます。)namedtuplelistnew_settings.lflagnew_settings[3]

一方、あなたのコメントから、 ^C は最初の 1 秒か 2 秒でしか機能しないように聞こえますが、joins のタイムアウトと関係があります。これは理にかなっています。メイン スレッドは 2 つのスレッドを開始し、2 つのjoin(1)呼び出しを行ってから終了します。したがって、起動後 2.something 秒で、すべての作業が終了し、ブロックを離れます。そのため、が をトリガーしてワーカー スレッドに終了を通知するtry:方法はもうありません。KeyboardInterruptloop_bool = False

そもそも s でタイムアウトが発生する理由と、タイムアウトしたjoinときに何が起こるかはわかりませんが、3 つの可能性があります。

  1. ^C まで終了したくありませんが、正当な理由でタイムアウトが発生しません。だからそれらを取り出してください。次に、メイン スレッドは他の 2 つのスレッドが終了するまで永遠に待機しますが、メイン スレッドはまだtryブロック内にあるため、^C を設定できるはずloop_bool = Falseです。

  2. アプリは 2 秒後に正常に終了するはずです。(2 秒のタイムアウトで、スレッドのペアで単一の join-any または join-all を好んだと思いますが、Python にはそれを行う簡単な方法がないため、スレッドを順番に結合しました.) この場合、loop_bool = Falseタイムアウトが終了したらすぐに設定する必要があります。を に変更するだけexceptですfinally

  3. タイムアウトは常に十分に寛大であると想定されており (おそらく、これは実際のアプリの単純化されたバージョンにすぎません)、タイムアウトを過ぎた場合、それは例外的な状態です。以前のオプションは、まだ機能する場合と機能しない場合があります。そうでない場合はdaemon = True、2 つのスレッドを設定すると、メイン スレッドが終了したときにそれらが強制終了されます (適切にシャットダウンするように求められるわけではありません)。(これが動作する方法は Windows と Unix では少し異なることに注意してください。ただし、このアプリでは Windows についてはあまり気にしないと思われます。さらに重要なことに、すべてのドキュメントには、「生きていない非デーモン スレッドが残っている」ため、クリーンアップを実行できるデーモン スレッドを当てにしないでください。クリーンアップを行っています。デーモン スレッドでは、一時ファイルや重要なログ メッセージが書き込まれないなどの可能性があるようなことをしないでください)。

于 2013-01-03T06:49:41.960 に答える