Python keyboard ではまったメモ

keybordモジュールを利用している時にはまったことをメモしておきます。
  動作環境
   Python 3.11.2
   OS BookwormPup64
   統合環境 VSCode

はまったこと

VSCodeのJupter Notebookで、下記のスクリプトを実行させていたとき、1回目の実行では問題なく動作していたのだが、同じセルをもう一度(2回目)実行すると、何も出力されなくなった。この原因が解らずに数日を費やしてしまった。完全に解決できていないけど、解ったことや推測をメモしておきます。

import keyboard

def test(e):
    print('callback : ',e.name)
    
keyboard.on_press(callback=test)

1回目のセル実行では図1の出力が、キーを押すごとに得られた。(期待通りの動作)
  

図1 セルの出力

2回目のセル実行では、何も出力されなかった。(???・・・)

下記のスクリプトで、出力先を変更すると、このセルの出力欄に1回のキー入力で、2つの出力があった。
  callback : shift
  callback : shift
上のスクリプトを、3回目の実行をさせて確認すると、1回のキー押下で3つの出力が、下記のスクリプトを記述したセルの出力欄に出力された(上のセルの出力欄ではない。図2)。以下、同様な動作が起こった。新たなセルでスクリプトは普通に動作するが、図2のセル出力欄にはキー押下したもの、他のセルでのprint文の出力がずっと出力され続けた。

これは、VSCodeのJupyter Notebookの環境の仕様だと思われる。sys.stdout=__sys.stdout__を実行したセルの出力欄(セル1とする)が標準の出力先となってしまったからだと思われる。現在のカーネルでは、どのセルでprint文を実行しても、その出力先はセル1の出力先となる。

元に戻すには、カーネルを再起動させるか、最初にバックアップをとっておき元にもどすかの方法がある。
  バックアップ 
   outj=sys.stdout のようにして、変数に退避させておく
  もとに戻す
   sys.stdout=outj で、元に戻る

import sys
sys.stdout=sys.__stdout__


図2 セルを3回実行したときの出力

解決できなかったことと対処法

VSCodeのJupter Notebookでは、
  ・1度実行させた keyboard.on_press() を停止させる方法がわからない
    def test(e): のなかで、sys.exit() を実行すると、
     keyboard.on_press() を再度実行させることができなかった
     (カーネルの再起動で復旧できる)
  ・2度実行させた時、keyboard.on_press()で呼び出される関数の中で実
   行しているprint文の出力先が不明。
    sys.stdout=__sys.stdout__を実行することで、keyboard.on_press()
    が2つ動いていることを確認できるが、もとの出力先は不明で、
    他のセルでのスクリプトは正常に動作しているように見える
    が、実は、keyboard.on_press()  は動作したままである。
               (keyboard.on_press() を実行させた回数分が動作している)

よって、対処法として、
   ・keyboard.on_press()の実行は、1度限りとする
   ・keyboard.on_press()を実行するするときは、カーネルの再起動
    をしてから実行する

ことにした。 

その他、keyboardモジュールについて気づいたこと

keyboard.read_key() について

 ・keyboard.read_key()の返り値は str 型で、押されたキーの名前、または
  キーコードであり、

       if keyboard.read_key()
         print(keyboard.read_key())

  のような使い方ができる。
  keyboard.read_key()が2回使用されていることに注意すること。
    1回目が、キーダウンのイベントによるもので、
     2回目が、キーアップのイベントによるものである。
  どちらも当然、返り値は同じキー名である。1回目がifの条件判定に
  利用され、2回目がキー名の出力に使用されている。
  1回のキー入力で発生した2つのキーイベントを、keyboard.read_key()
  を2回使用することで使い切っている。(これが大切)

  ifの条件判定は、1文字以上の文字列は「True」と判定されるので、
  キーの名前が0文字でキーのコードが0であるようなキーがない限り、
  キーを押せば、if文内は必ず実行される。
  これは、次のようなスクリプトで確認できる。

        if input():
         print('hello')

       何かのキー入力で hello と出力される
       enterのみを入力すると、hello は出力されない

 ・keyboard.read_key() は
   何かのキーが押されるまで、入力待ちがおこる
   
(input文と同様の動きをする)
    

     a = keyboard.read_key()
          この文で入力待ちがおこり、何かのキーを押すことで、
        aには、押されたキーの名前が代入される。何かのキーが押さ
       れない限り次の処理には進まない。

     注意したいのは、次のような使い方をしないこと。
       a = keyboard.read_key()
       b = keyboard.read_key()
      この結果は、a、b  には、同じキーの名前がセットされる。

  keyboard.read_key()を使用するときは、
    ・何らかのキーが押されるまで、待ち状態になり次に進まない。
    ・キーdownとupのそれぞれのイベントで同じキー名を返してくる
     ので、keyboard.read_key() を1回だけ使用した場合は、up分
     のイベントが残っていることに注意すること。

     (2回続けて、近くで使用する。1回しか使用しない時は、
      upイベントの処理をその場でしておく。)

 ちなみに、keyboard.read_key()は、次のように定義されている。

def read_key(suppress=False):
    """
    Blocks until a keyboard event happens, then returns that event's name or,
    if missing, its scan code.
    """
    event = read_event(suppress)
    return event.name or event.scan_code

最後に

モジュール keyboard は便利であるが、挙動をよく調べて使用する必要がありそうだ。入力待ちするものとしないもの、キーイベント待ちが発生したままになっているもの。詳しく調べきれていないので、今後、使用するものを1つ1つ確認しながら使っていこうと思う。また、webで検索し調べたものの中には、今回のような点について詳しく述べられたものが少なく、少しはまってしまった。

解決できなかった点は、残念です。いつか解明できたら追記します。

調べたものは、今後、追記していきます。こんなことで悩んでいるのか程度に流し読みしていただけたら幸です。(私は初心者です。記述に誤りがあるかもしれません。ご注意ください。)

この記事が気に入ったらサポートをしてみませんか?