PythonでSetIntervalとSetTimeoutと同じようなことがしたい。

はじめに

JavaScriptで作ったタイピングゲームをPythonに移植しようと思いました。JavaScript版では、カウントダウンタイマーをSetIntervalで実装していますが、同等の実装をPythonでどうするか調べてみました。

結論

  • PythonはJavaスレッドモデルをベースにしたthreadingパッケージがある
  • 一定時間後に、関数を呼び出したい場合は、threading.Timer。
  • 定期的に関数を呼び出したい場合は、threading.Threadを継承し、スレッドモデルで実装すればよい。

インターネットを調べていくと、質問が「SetIntervalのような定期実行をしたい」という質問のため、回答が関数を再帰的に呼び出し、スレッドを毎回生成しているコードを見つけました。(Python Equivalent of setInterval()?) これがPythonとして良いのかどうかはわかりませんが、毎回スレッドを生成するのに違和感があったので、もう少し調べたところ、threading.Threadを継承したサンプルを多く見つけました。

SetTimeoutのように、一定時間後に処理を実行する場合は、threading.Timer

threading.Timer(delay, task)で、待機時間を指定します。また、スレッドプログラミングなので、joinも使えます。

 

Pythonで定期的に関数を実行したいのでSetIntervalを調べる

標準ライブラリーでは用意されていないようです。実装するにはthreading.Event(),event.set(),event.wait()の知識が必要なようです。

少し調べていると、PythonのスレッドはJavaのスレッドモデルを参考にしているので、JavaScriptのSetIntervalで定期的に実行するのではなく、スレッドモデルの書きかたで実装すればよいだけでした。

もう少し具体的に言うと、threading.Threadクラスを継承したクラスを作成し、runメソッドを好みに書き換えればよいだけです。Pythonには、スレッド間通信するための便利なEventオブジェクトがあります。これでwaitの管理などをします。

ちなみにJavaはThread.sleep,Thread.join,Thread.interruptのようにEventオブジェクトではなく、Threadもしくはそのインスタンスでwaitなどを管理します。

基本的なコード

 

カウントダウンタイマー

上記ソースコードをベースに、カウントダウンタイマーを作りました。Countdownクラスを初期化するときの引数にcountで残り秒数を設定できるようにしました。

 

一定間隔で関数を呼び出す

厳密ではないですが、ひとまず一定間隔で呼び出せる。

厳密な間隔で呼び出したいとき

runメソッドのwhileループのチェックで、time.time()を導入します。以下をまずはコピペしてprintでいろいろ出力しながら試すのが簡単だと思います。

https://stackoverflow.com/questions/2697039/python-equivalent-of-setinterval/48709380#48709380

 

 

 

 

Python2と3がインストールされている環境で、venv仮想環境を用意してvscodeで開発をする方法

はじめに

Windows10で、Python3+venvを使い、環境を汚さずにプロジェクトの仮想環境を作りvscodeで開発するための初期設定メモです。

私は、Windows10に、Python2.7とPython3がインストールされている環境で、pythonを実行したらPython2が実行されpy.exeで切り替えられる環境になっています。

仮想環境venvと起動終了について

Pythonはpipコマンドでインターネット上のライブラリーを取得し使えます。ある程度使うと、PCごとのライブラリー管理ではなく、プロジェクトごとに管理したくなります。

以前は様々な仮想環境がありましたが、現在は標準でvenv仮想環境がインストールされます。以下のように1つのコマンドで簡単に仮想環境が用意できます。-m venvでvenvモジュールを使って、pyコマンドでpythonを実行します。また<name>に任意の名前を付けれますが、venvとする場合が多いようです。

ちなみに、py.exeで2か3どちらをデフォルトで起動するかは、PY_PYTHONで変更できます。py.iniファイルを記述する方法もあります。

venv仮想環境を用意した後、使うにはacivateし、使い終わったらdeactivateをします。仮想環境を用意したら<name>/Scripts/Activate.ps1とDeactivate.ps1が用意されるのでこれを実行すれば良いです。

実際にやってみる

hello_myprojectという名前でフォルダーを作成し移動

venvという名前で仮想環境作成

vscode起動

vscode上で CTRL+@ でPowershellを起動し、vscode上のPowershellで仮想環境を使えるようにする。

venvフォルダーにtest.pyを作成する。

この時に、Python3.6.x(venv)をvscodeが見つけて、pylintのインストールを促すので、Installします。以下のようにpylintが失敗する場合もありますが、

ターミナルは、(venv)のようにすでに、Python3のevnvが有効にされた状態なので、「python -m pip install –upgrade pip」を実行し、その次に「pip install pylint」を実行します。

あとは、このまま開発を続けられます。仮想環境はアクティベートが必要なことを忘れないようにしましょう。OSを起動し直したら、当然仮想環境は停止した状態なので、再度アクティベートする必要があります。

まとめ

Windows10で、Python3+venv+vscodeの環境を構築しました。

venv仮想環境は、アクティベート、ディアクティベートで起動、停止を行います。

プロジェクトフォルダーに、venv仮想環境(今回はvenvという名前にした)を用意して作業します。仮想環境名=プロジェクト名にして管理したくなりますが、そうするとvscodeが単純に認識しなくなるようです。

 

 

GTK+でボタンを右下に持っていき、ウィンドウ拡大に追随させる方法

GTK+のウィンドウの拡大縮小時に、ボタンを追随させる方法が分かってきました。以下のGladeのように、まずGtkBoxをウィンドウに貼り付けて、「ウェジットの間隔」にある「広げる」と「アライメント」を調整します。水平アライメントを「最後」に設定すると、ボタンが右に配置されます。垂直アライメントを「最後」に設定すると、ボタンが下に配置されます。この2つを行うことでボタンが右下に配置されます。

また、ウィンドウが大きくなったり小さくなった場合に追随させるには、「広げる」項目を画像のようにラジオボタンにチェックを入れて、「オン」にします。

他にGtkAlignmentというオブジェクトもあります。これをボタンの親オブジェクトにして、同様に右下に持ってくることも可能なようです。

DebianLinuxでGTK+3.0のチュートリアルを試してみた感想

Go言語で、GUIを調べていたらGTK+3.0のバインディングがあるそうです。GTK+はバインディングが豊富でスクリプト言語などでも使えます。良い機会なのでDebian LinuxでC言語を使ったWindow表示を試してみました。

Programer’s Notes – GTK Glade C Programming

が初心者向けのチュートリアルで1つの記事の量も少なく、わかりやすいです。

開発環境構築とHelloworld

https://prognotes.net/2015/06/gtk-3-c-program-using-glade-3/

上記チュートリアルは、はaptでgtkライブラリーのインストールやビルド方法も乗っていて簡単にWindowを表示できました。pkg-config によりライブラリーのインクルードやリンケージは自動です。またビルド時に-export-dynamicが必要になります。最初これをつけていなくてビルドがうまく行きませんでした。

GTK+はGUIビルダーGladeがある

GTK+はWin32APIのように、ほぼ全てをテキストエディターで書いていくと思っていたのですが、かなり昔からGUIビルダーのGladeがあり、これでUIを作成しながら、任意のエディターでコードを書いていけます。

Geanyエディター

Geanyは、タグファイル が用意されているので、gtk-3.16.6.c.tags をダウンロードし、[ツール]->[Load Tags]で、タグファイルを読み込むことで、コード補完が出来ました。

gladeファイルの読み込み

GLADEでGUIを作成した場合は、gtk_builder_add_from_file関数で、.gladeファイルを指定できるのでそれで、ソースコードと.gladeファイルの連携が可能になります。main.cを作成して、GLADEで、window_main.gladeを作成し、無事にWindowを作成できました。

APIは、C++のようにオブジェクトがメソッドを持つわけではないため、gtk_object_xxxxのような形になりますが、補完ができたのでそこまでストレスはありませんでした。

GTK+3.0でWindowを表示して、日本語のタイトルをつけれた!

GTK+3.0の開発はかなり軽い

WindowsのHyper-V仮想環境でDebian+LXDE で動かしていますが、GUIビルダーもエディターのGeanyもホストの環境と同じように快適に動かせています。Ubuntu をデフォルトでインストールすると非常に重いので、先入観でGTKの開発は重そうなイメージを持っていましたが、超軽量でした。

 

GAE/Goのblobstoreを使ったCSVアップロードとCSV読み込み

経緯

別のソースコードでCSVアップロードを試していたら、コードの書きかたが正しくないか、github.com/gorilla/muxの使い方に問題があって、CSVデータをテキストでブラウザーに表示するはずがダウンロードになってしまい問題が解決できませんでした。

対応

github.com/gorilla/muxなどや他のソースコードを含めず、単純なCSVファイルをアップロードし、それをブラウザーでテキストとして表示するサンプルを書きました。

これはyomusu/csvblob.goをフォークしました。 context型やappengineのインポートを変更していますが、基本処理はyomusuさんのコード其のままです。

このソースコードでdev_appserver.py上で問題なく動作しました。

gcloudのアップデートに失敗し動かなくなる

Google App EngineはPython2でインストーラーが正常に動作いないし、SDKアップデートができずに壊れた。

 

gcloud feedbakでレポート送れると書かれているけれど、gcloudコマンド壊れて送信できない。

 

GAE/Goは、$GOPATHにあるアプリで参照されているファイルをアップロードする

現在、GAE/Goで、Cloud Translate APIとDatastoreを使った中国語から日本語の単語翻訳、単語帳アプリを作っています。ひとまずデータ登録などをGAE/Goできて以下のサイトの「goapp は $GOPATH 以下もアプリケーションのソースとしてアップロード/コンパイルする」というのが気になったので調べてみました。

記事でちゃんと書いてありますが、自分が作ったGoアプリのimportで使っているファイルすべてがアップロード対象になります。つまり$GOPATHにあるアプリで参照されているファイルです。当たり前ですが$GOPATH/src全てではありません。(私は、何を読み間違えたのか、$GOPATH/srcが対象と勘違いしました。)

実際に確認してみる

どのファイルがアップロードされるかはデプロイ時に–verbosity=infoを付けるだけです。appdata環境変数にもログが出力されます。

通常gcloud app deployコマンドを実行すると[Do you want to  continue(Y/n)?]と表示されますが、–verbosity=infoにしていると、この入力待ちの時にログから、これからアップロードするファイルを確認でき、実際にエクスプローラーでも確認できます。

 

GAE/Go本番環境では、インデックスを再構築しないと動作しない。

開発環境で動作していたのですが、本番環境で動作しなくなりました。Cloud Consoleからログを確認したところ、一致しないインデックスを見つけたというログがありました。

どうやらindex.yamlがある場合は手動で反映させる必要があるようです。

インデックスの再作成と古いインデックスの削除

上記を実行後にすぐにブラウザーでアプリを試してみたら、エラーが変わり以下になっていました。

API error 4 (datastore_v3: NEED_INDEX): The index for this query is not ready to serve. See the Datastore Indexes page in the Admin Console.

「インデックスがまだ出されてません。AdminConsoleのインデックスページを見てください。」とのことなのでしばらくしてからアプリを試したら問題なく動作できました。

Cloud Datastoreのクエリーを変更したりする場合は注意する必要があります。

開発環境でのインデックスのクリア方法

dev_appserver.pyを起動するときのオプションに–clear_search_indexes=trueを付けるとインデックスをクリアしてくれます。

 

index.yamlが自動生成されて動かなくなったdev_appserver.pyを元通りにする

kindやAncestorを試しているうちに動かなくなった

開発途中でいきなりdev_appserver.pyが正常に動作しなくなり以下のログを出力しました。状況としては、

  • dev_appserver.pyは起動問題なし
  • localhost:8000で、datastoreをブラウザから見ようとすると、以下と同様なエラーがブラウザーに表示
  • アプリの登録処理ができない

原因は、index.yamlの自動生成

自動でindex.yamlが生成されていた事でした。このファイルは、独自にインデックスを用意する場合に使うのと、開発サーバーが自動生成したインデックスをここに書き込んでくれるようです。

https://cloud.google.com/appengine/docs/standard/go/config/indexconfig

DatastoreのkindやAncestorを試しているうちにこのindex.yamlが作られたようです。

対応方法は、不要なインデックスをindex.yamlから削除

j上記のコメントの後に、自動生成されたインデックスが書かれていますがコメントアウト、または削除しdev_appserve.pyを起動し直せばよいです。inde.yamlファイル自体を削除すると、インデックスが必要というエラーが別に発生しました。

 

その他

この問題を調べているうちに、datastoreの保存先やクリアの仕方が分かりましたのでメモを残しておきます。

clear_datastore=yesでdatastore削除

clear_datastore=yesとclear_search_indexes起動オプションでデータ削除できることが分かりましたが解決できませんでした。

物理的に削除

上記オプションでdev_appserver.pyを起動すると、以下のようappengine.Noneがあり、これを削除すればよいと分かりました。

https://stackoverflow.com/questions/25831141/google-app-engine-datastore-storage-default-path

削除したけれど解決できませんでした。

Ctrl+Cで終了してみると以下のようにpending transactionsと読み取れるのでこの失敗したデータ登録がどこかで保持されているので、データベースを削除しても実行されているようです。

 

 

 

html/templateでrangeでindexを使い別のスライスを参照する方法

Go言語のhtml/templateは、スライスやMapオブジェクトに対して{{range $index, $element := xxxxx}}のような記法でインデックスが使えます。

例えば、[]Userと[]Keyという対になったスライスが2つある場合、UserをRangeアクションでループ処理している間に、Key[i]のようにして別のスライスにアクセスしたい場合があります。(本当に汎用的な解決策はおそらくzip関数。)

このような場合は、以下の記述で[]Userと[]Keyのそれぞれの要素にアクセスできます。html/templateでは、{{range $i, $user := users}}でインデックスと要素にアクセスできるため{{$i}} {{$user}}で表示しています。またrangeスコープないなので、ドット{{.}}は、現在のuser要素を表示します。最後に、通常のプログラミング言語でKeys[i]やUser[i]のように要素取得するのを、{{index $.Keys $i}}で行っています。index関数の第一引数が、Keysスライス、第二引数がインデックスという意味です。

また、$.Keysがポイントで、rangeスコープ内で、ドットを指定するとuser要素になりますが、$.とすることでグローバルを参照します。

 

 

結果