SyntaxHighlighter

2017年8月16日水曜日

Microsoft Edge の Native Messaging を使った拡張機能 その1 (UWP を用いた Native Messaging 拡張機能の概要)

Microsoft Edge の Native Messaging を使った拡張機能 その1 (UWP を用いた Native Messaging 拡張機能の概要)

Firefox, Chrome に続いて、 "Open TortoiseSVN" を Microsoft Edge にも移植してみたので、そのまとめです。

もう少し記事を分けるかもしれませんが、以下のような順番で書いていこうと思います。

  • その1: UWP を用いた Native Messaging 拡張機能の概要
  • その2: パッケージングの方法と、 Win32 アプリケーションの起動

なお、最終的なソースコードは GitHubの Open TortoiseSVN for Microsoft Edge にあります。

Edge の拡張機能のサポート

元々 Google Chrome の拡張機能で導入された拡張機能の形式ですが、 Mozilla Firefox にも WebExtensions として導入され、 Edge でも同様の機能がサポートされ始めていました。

具体的には、2016年の Anniversary update 以降、 Microsoft Edge も Chrome や Firefox とほぼ同様の拡張機能をサポートしています。
そして、 2017年の Creators update で、 Native Messaging のサポートも開始されました。

詳しい内容は Native messaging in Microsoft Edge のページに載っているので、基本的な作成方法などはこちらを参照するのがよいでしょう。

この記事では実際に上記のページに従って拡張機能を作成した際に、気になったところを中心に書こうと思います。

また、 Microsoft によるサンプルは SecureInput として GitHub で公開されています。

Firefox, Chrome との違い

Edge での Native Messaging サポートは、 Firefox, Chrome とは大きく違います。

Native messaging in Microsoft Edge のページに書かれているように、下記が特徴的です。

  • JavaScript で書かれた Extension は、 UWP 形式のアプリケーションとのみ通信できる。 Win32 形式のアプリケーションとは通信できない。
    Firefox, Chrome の場合は、 (Windows であれば) Win32 形式のアプリケーションと通信できる。
  • UWP アプリケーションとは、 AppService の仕組みを用いて通信を行う。
    Firefox, Chrome の場合は、標準入出力で JSON 形式のデータを所定のフォーマットでやり取りすることで通信を行う。

つまり、外部アプリケーションと通信する拡張機能の JavaScript 側のインターフェースは Firefox, Chrome と (ほぼ) 同じですが、それを受ける側であるアプリケーションの仕様は、まったく異なることになります。

なお、 Edge の Native Messaging 拡張機能の特徴として二つ書きましたが、実際には「外部のアプリケーションとは AppService の仕組みで通信を行う」という部分が重要です。
これは、 AppService は UWP アプリでしか作成できないので、必然的に UWP 形式のアプリケーションが必要条件となるためです。

UWP アプリとの通信

作成する AppService は、通常のものとまったく同じものです。なので、 UWP アプリで AppService を作成する方法については、他のページを探してみてください。
ここでは、 Edge との通信部分について書きます。

例として、仮に、 com.example.uwp という名前の AppService を作成するとします。

JavaScript 側のコード

その場合、 JavaScript 側から UWP アプリを呼び出すのは以下のようになります。

このように、 browser.runtime.sendNativeMessage もしくは browser.runtime.connectNative の第一引数で指定する application の名前は、 AppService の名前そのものになります。
それ以外は、 Firefox, Chrome の場合と何も違いはありません。

UWP 側のコード

AppService を実装する方法としては、 in-process な実装と、 out-of-process な実装の二種類があります。
詳しくは Microsoft の説明などを見るとよいでしょう。

Open TortoiseSVN では in-process な方法で実装したので、下記を実装する必要があります。

OnBackgroundActivated
他のアプリケーション (今回の場合は Edge) から AppService への接続があった場合、この関数が呼ばれます。
通常の AppService と同じように、この中で Connection に対してイベントハンドラを登録します。
OnAppServiceRequestReceived
他のアプリケーション (今回の場合は Edge) からメッセージが送られてきた際にそのメッセージを処理するためのイベントハンドラです。
Native Messaging のメッセージ処理の手順に合わせて実装します。

OnBackgroundActivated

OnBackgroundActivated の概要としては下記のような実装になりました。

こちらに関しては、特に通常の AppService と違いはないはずです。

OnAppServiceRequestReceived

この関数では、 Edge からのメッセージを受け取り、それに応じて処理を行います。

概要としては、以下のようなことをすればよいです。
ここでは、上で書いた例のように JavaScript 側から {"action": "action1", "args": ["arg1", "arg2", "arg3"]} という引数で呼ぶことを考えてみます。

いくつか重要な点を挙げておきます。

Edge からのメッセージは ValueSet の一つ目の Value として渡される。

ValueSet は辞書のように Key, Value の組を保存できるものですが、 AppService ではこの ValueSet を用いてデータをやり取りすることになっています。そのため、 Edge の Native Messaging 拡張機能も、 ValueSet でデータをやり取りすることになります。

Edge から送られてきたデータは、 JSON 形式の文字列となって、渡された ValueSet オブジェクトの最初の (唯一の) 要素の Value に格納されてきます。

Key は "Message" になっているようですが、これを仮定しないのがよいと思います。

UWP アプリからのメッセージも同様に ValueSet として返す。

逆方向もまったく同じです。
Key は適当な名前でよいので、 Value に JSON 文字列を指定し、 SendResponseAsync でメッセージを送ります。

JSON 形式にシリアライズするためには JavaScriptSerializer と匿名型を用いるのが楽でよいと思います。

なお、本筋とは関係ないですが、やや複雑な JSON 文字列をパースする場合、以下のようにすればよいです。
例えば、 {"action": "action1", "args": ["arg1", "arg2", "arg3"]}args をパースする場合は、例えば以下のようにすればよいです。

このようにすることで、 "args" の中身を配列で取り出すことができます。

あとは UWP アプリ内で好きな処理をすればよいわけです。

注意点

Microsoft EdgeHTML 15.15063 の時点では、 UWP アプリからの戻り値が (私の予想と) やや異なった形式で返ってきます。

具体的には、下記のようになります。

このように、 Firefox, Chrome と違ってわざわざ JSON.parse する必要があります。

私はこの挙動はバグのように思いましたので、 issue として登録しましたが、まだ有効な回答はない状態です。

続いて、作成した UWP アプリを JavaScript のコードとまとめてパッケージングし、 Edge の拡張機能として登録することになります。

次回以降は、下記について書く予定です。

  • Appx パッケージの作成
  • Win32 アプリケーションの起動
  • browser.runtime.sendNativeMessagebrowser.runtime.connectNative における注意点

2017年4月23日日曜日

Pausable Unittest その2 (開発の経緯)

Pausable Unittest その2 (開発の経緯)

概要

今回は、なぜ Pausable Unittest を Stackless Python のライブラリとして作成したかについて書いておこうと思います。

なお、ここで言及している Python は、基本的には Python 2.7 での話です。
最新の Python 3.5, 3.6 系列ではやや異なっているかもしれません。

Pausable Unittest 実現に必要な機能

Pausable Unittest では、以下のようなスクリプトを書くことを目的としています。

この中で重要なのは、(*1) の部分で Python インタプリタを終了し、システムを再起動した後で、続きを実行できるところです。

これを実現するためには、「関数の実行の中断と、その状態の保存/復元」が必要になります。
self.reboot() では、実行中の関数 test_check_reg を中断し、そのローカル変数の状態やどこまで実行したかの状態を含めて保存し、再起動後に復元する必要があります。

generator, fiber, coroutine, continuation

そこで、この機能を実現するために必要な要素技術は何か、について、検討をしました。
その結果、ジェネレータファイバーコルーチン継続などを、ファイルに保存し、あとで復元できればよいという考えに至りました。

ジェネレータ、ファイバー、コルーチン、継続は、いずれも「関数のようなものを途中で中断し、他に制御を移すことができる」という機能を持っています。

例えば、 JavaScript で書いたジェネレータでは、下記のようになります。

上記の例では、1, 2, 3, ..., 5 の順で数字が出力されます。
つまり、g.next() でジェネレータ gen に制御を移し、 yield で制御を戻す、といった流れになります。

これは、関数のような genyield によって中断できていることになります。

なお、 JavaScript ではジェネレータを定義する際には通常の関数と異なり function* を用いますが、 Python では「関数の中に yield があればジェネレータ」となっています。

参考までに Ruby のファイバーは下記のようになります。

こちらも同様に、1, 2, 3, ..., 5 の順で数字が出力されます。

このように、ジェネレータやファイバー (やコルーチン) には、「処理を中断し、処理を他に移すことができる」という特徴があります。

あとは、途中まで実行したジェネレータやファイバーを、ファイルに保存できればよいことになります。
先に挙げた Ruby の例では、下記のようなことができるとよいわけです。

しかし、これは Marshal.dump に失敗し、例外が送出され、うまく動作しません。

通常の Python でジェネレータを使って同様なことを書くと、やはりジェネレータを pickle できず、同様にうまく動作しません。

Stackless Python を選んだ経緯

通常の Python や Ruby で実現できない理由

通常の Python や Ruby で実現できないのは、もちろん「ジェネレータやファイバーをファイルに保存できないから」です。
これは、ただ未実装なわけではなく、技術的に実装が困難だからです。

Python や Ruby でのメソッドの呼び出され方

通常の Python や Ruby では、メソッドの呼び出し時にスタックが消費されます。
例えば、下記のような Python のスクリプトを考えます。

この関数 foo は、バイトコンパイルの結果、以下のようなバイトコードに変換されます。

Python は、与えられたソースコードを上記のようなバイトコードの変換した後、実際に処理を行います。
このとき、各行の命令を実行していくわけですが、ここで関数呼び出しの OpCode である CALL_FUNCTION がどのように処理されるかに注目してみます。

Python での CALL_FUNCTION の処理

現在の Python や Ruby では、ソースコードをバイトコードにコンパイルした後で、それを仮想マシン上で実行するようになっています。
Python の場合、上記のようなバイトコードに変換した後で、実行していきます。

「関数の呼び出し」の場合、通常は CALL_FUNCTION という OpCode に変換されます。
その後、実行時に ceval.cPyEval_EvalFrameEx 関数の中の switch 文で解釈されて実行されます。
Python 2.7.10 のコードを一部だけ抜き出すと、下記のようになっています。

このように、関数の実行そのものは call_function に委ねられ、処理が続けられます。

call_function では、いくつかの条件によって処理が分岐していきますが、間接的に PyEval_EvalFrameEx 関数が呼ばれます。
たとえば、ある条件下では fast_function が呼ばれますが、その中を追っていくと、条件に応じて PyEval_EvalFrameEx が呼ばれるのが分かります。

つまり、 Python インタプリタでは、 Python の関数呼び出しを Python 仮想マシンが実行する際に、 PyEval_EvalFrameEx の再帰呼び出しが発生し、その結果、スタックが消費されていることが分かります。

スタックが消費されることによる影響

スタックが消費される実装になっている場合、関数の状態を復帰させるのが非常に困難になります。

C 言語でのスタックの使い方は完全にコンパイラに依存しています。
例えば、スタック上には種々のローカル変数が配置されますが、それらをダンプしたり、復旧させたりする手段は、 C 言語には提供されていません。
もちろん、環境や OS 依存な方法を使ってスタックをダンプすることはできますが、ダンプしたスタックをそのまま復旧しても、状態が正しく復元されるわけではありません。
スタック上には種々の変数のアドレスなどがある場合もありますが、スタックがダンプされたときと復旧させた時で同じアドレスが使われるとは限らないからです。

そのため、スタックがある程度消費された状態を復元するのは、困難なものになります。

Stackless Python での CALL_FUNCTION の処理

一方で、 Stackless Python では、以下のようにやや処理が異なっています。

このように、 STACKLESS マクロに応じて、goto によるジャンプなどがはさまっています。

Stackless Python では、 call_function の中では、 Python の関数呼び出しは行わず、すべての処理が PyEval_EvalFrameEx のループに戻ってから行われるように工夫されています。
俗に「トランポリン呼び出し」と呼ばれるような方法に近い実装がされています。

その結果、 Stackless Python では、(一部の例外はありますが) Python の関数呼び出し時に、原則として再帰呼び出しが行われません。

こうした実装上の工夫や、 prickelpit.c などで追加されている pickle に関する処理により、 Stackless Python ではジェネレータを pickle することが可能となっているようです。

Stackless Python を選択する

主に、これらの技術的な理由により、個人的に好みの Ruby ではなく、 Stackless Python を選ぶことにしました。
また、 PyPy でも、同様にジェネレータを保存できるので、 Python を選んでおけば、仮に将来的に Stackless Python の開発が止まっても、ある程度の代替案が確保できるだろうという目論見がありました。

おそらく、 Scheme や Lua などでも同様のことができるのかもしれませんが、こういった理由により、 Pausable Unittest を実装する言語として、 Python を選択しました。

2017年1月21日土曜日

Ruby で net/http を使ってファイルなどを multipart で POST する

Ruby で net/http を使ってファイルなどを multipart で POST する

今回は、自分用の覚え書きです。

net/http を用いた multipart の POST

Ruby で HTTP クライアントを書いていると、しばしばファイルなどを POST したいことがあります。
この場合、 multipart なデータを POST することになります。

この機能は、標準ライブラリである net/http のみを用いて、割と簡単に実現できます。

よくネットで見つけられる set_form_data ではなく、set_form を使うのがポイントです。

set_form(params, enctype="application/x-www-form-urlencoded", formopt={}) の引数

params

set_form の一つ目の引数である params には、配列の配列を渡します。
それぞれの配列は、以下の形式になります。

[ (パラメータ名), (値 or ファイルオブジェクト), (オプションのハッシュ) ]

上の使用例を見れば簡単に理解できると思いますが、オプションのハッシュを指定することで、ファイル名や種類を指定することができます。

これらの値の組を配列として指定することで、送信するデータを決められます。

enctype

二つ目の引数である enctype には、 "multipart/form-data" を指定します。

デフォルトは "application/x-www-form-urlencoded" なので、ファイル送信するときは、必ず指定する必要があります。

formopt

三つ目の引数である formopt には、 :charset:boundary を指定することができます。

:boundary は、指定しなければランダムで URL-safe な文字列が生成されて使われます。
そのため、通常は特に指定する必要はないでしょう。

以上のような引数を指定し、 set_form を使うことで、簡単にファイルなどを multipart で送信することができます。

なぜか日本語のリファレンスから漏れており、(そのせいか) 日本語の記事があまりないようなので、メモとして書いておきます。
暇があったらリファレンスに載せてもらうようにお願いする予定です。

2016年11月4日金曜日

Open TortoiseSVN の Native Messaging 対応 (Firefox)

Open TortoiseSVN の Native Messaging 対応 (Firefox)

Firefox 向けの Open TortoiseSVN を WebExtensions 化したので、そのまとめと思うことをダラダラと書きます。

Firefox の WebExtensions

Firefox では、従来の形式のアドオンを将来的に廃止し、 WebExtensions という新しい形式のアドオンへ移行しようとしています。

WebExtensions は、内容は Google Chrome の拡張機能とほぼ同じものです。
従来のアドオンとの大きな違いは、アドオンでできることの自由度です。
従来のアドオンでは、 Firefox を構成する XUL, JavaScript の非常に幅広い部分を自由にカスタマイズできたのに対し、WebExtensions では、あらかじめ Firefox 側から提供された部分しか拡張できません。

ブラウザを作る側からすれば、あまりに自由にアドオン側にカスタマイズを許すと、互換性を保ちながら内部構造を大きく変更することが困難になり、マルチプロセス化など大規模なコード変更ができなくなるなどの弊害があったようです。

Firefox ユーザーの中には、「自由度が減る」ことを嫌って WebExtensions への流れに反発している人も多いようです。
個人的にはこの心情はよく理解できます。
私もそうなのですが、今でも Firefox を使っている人には、 Firefox の「自由なウェブブラウザ」という考え方を好む人がいるように思います。
そういった場合には、アドオン作成者の自由度が減る方向になるのは、受け入れづらいのだろうと思います。
ブラウザをメンテナンスする側の気持ちとしてはよく分かるのですが。

WebExtensions の Native Messaging での注意点

Open TortoiseSVN では、外部ツールである TortoiseSVN を開く必要があります。
Google Chrome 版と同様に、 Firefox の WebExtensions では、 Firefox 50 以降でサポートされた Native Messaging を使う必要があります。

Google Chrome 版とほぼ同様なのですが、一点だけ注意する必要がありました。
それは CreateProcess 関数へのフラグ指定です。

Firefox の Native Messaging で起動されるプログラムから、さらに外部プログラムを実行する場合、Windows では CreateProcess で起動することが多いと思います。
このとき、 CreateProcessdwCreationFlags として CREATE_BREAKAWAY_FROM_JOB を指定する必要があります。
より正確には、 Firefox 50 では不要なのですが、 Firefox 51 では必要となるので、注意が必要です。

Firefox 51 で Bug 1290598 - Migrate native messaging tests to xpcshell への対応の一つとして Manage and terminate Windows subprocess trees as a single job object. の変更が加えられました。
これにより、 Native Messaging で起動された外部プログラムのプロセスは、ある一つの Job Object に結びつけられることになります。
よって、その Job Object が Terminate されるタイミングで、 CreateProcess で起動された子や孫のプロセスも、同時に終了されることになります。

CREATE_BREAKAWAY_FROM_JOB を指定しない場合、起動した TortoiseSVN は、 Job Object の終了とともにプロセスが終了されることになり、うまく動作しませんでした。
CREATE_BREAKAWAY_FROM_JOB を指定することで、 TortoiseProc.exe を Job Object に関連付けず、プロセスの終了を防ぐことができます。

これは MDN Native Messaging の Closing the native app に書かれている内容ですが、知らずに Google Chrome から移植すると、何が起こっているのか分からずかなり悩むことになりました。

なお、同様に悩まれた方が Bug 1301246 - extension will close native client and sub processes those native client created で議論されており、結果として上記の MDN Native Messaging に注釈が加えられたようです。

こういった Google Chrome 拡張機能と Firefox のアドオンの微妙な違いは、今後も残ってしまうのでしょうか。
ちょっと面倒です。

2016年9月22日木曜日

Pausable Unittest on EFI Stackless Python - PyCon JP 2016 での発表を終えて

Pausable Unittest on EFI Stackless Python - PyCon JP 2016 での発表を終えて

PyCon JP 2016 で発表する機会をもらいました。
Pausable Unittest on EFI Stackless Python というタイトルで発表してきました。

発表資料

発表資料を公開します。
ちょっとだけアニメーションが崩れていますが、さすがに Microsoft 純正だけあって docs.com の再現はすばらしいです。

発表動画

発表の様子はこちらの動画から見られます。

発表に至る経緯と、簡単な感想

経緯

一度くらい社外のカンファレンスに出たいという気持ちがあったので、ちょうどいいネタがあったこともあり、 Python 歴は半年ぐらいですが、 talk に申し込んでみました。
また、開発職についたこともあって、大学時代と異なり外で発表する機会がなかったので、社外の雰囲気やトレンドを知っておきたい、ということも動機の一つでした。

talk への申し込みは、学会の発表を簡単にしたようなもので、簡単なプロポーザルと自分の紹介などを書き、採択会議の方で採択/非採択 が決まる、といった形でした。
採択率は 48/108 とのことで、「適当に出すと通らないが、そこそこきちんと書けば通る可能性も高い」といったところだと思いました。

感想

初めての、学会以外のカンファレンスでの発表でした。
残念ながら、聴衆の方の人数はそれほど多くはなかったのですが、その分、特に緊張することもなく発表を進めることができました。

発表では、一点だけ想定外のことが起こりました。
VirtualBox 上で、その場でデモをしようとしていたのですが、 VirtualBox 上の OS を再起動すると、なぜか VirtualBox のウィンドウがプロジェクター側から消えて内蔵液晶側に移動してしまう、ということが起こりました。
私のデモでは再起動を繰り返すので、致命的だったのですが、用意していたムービーに切り替えることで、そのまま進めることができました。
念の為と思って作っておいたムービーでしたが、あって助かりました。

また、聴衆の方にはきちんと発表の内容は伝わったようで、いただいた質問も非常に鋭いものでした。大変ありがとうございました。
こういった直接のやり取りができるのは、カンファレンスで発表することの、大きなメリットなのだろうと思います。

発表冒頭でアイスブレイク的に触れたのですが、私は基本的には Ruby が好きで、 Python を触り始めたのは半年ほど前のことでした。
そのため、 Python の文法は分かりますが、コミュニティの文化はよく分かっておらず、カンファレンスへの参加はやや緊張するものがありました。
いくつかの発表は、毎年の恒例になっているものもあるそうですが、そういったノリにはついていけず、とまどうこともありました。
ただ、私のような新規参加者が壁を感じるかといわれると、そうではなく、非常によい雰囲気だったと思います。

今回、私が発表したライブラリは、「unittest を一時的に中断し、後で続きを継続する」というコンセプトなのですが、似たようなことをしている David Weil さんに話を聞けたのはとても有意義でした。
彼の発表は私の発表と時間が重なっていたので、彼にお願いして昼休みに個人的に発表を聞かせてもらいました。
知りたかったことも直接の質問で確認することができたり、お互いに意見交換して発表スライドをその場で充実させたりと、よい経験ができました。

そういうわけで、初めての PyCon JP への参加は、とても楽しいものとなりました。
スタッフの皆様を始め、 PyCon JP に関わられた方々、大変ありがとうございました。

2016年5月7日土曜日

Pausable Unittest その1 (continulet と tasklet)

Pausable Unittest その1 (continulet と tasklet)

概要

初めて Python 向けのライブラリを書きました。
Pausable Unittest という名前で、名前の通り unittest の亜種です。

このライブラリは、通常の unittest と異なり、途中で中断することができます。

今回は、このライブラリの目的と、利用している PyPy, Stackless Python の技術についてまとめます。

目的

PC に限らず、家電や組み込みデバイスなどの開発をする場合、再起動をはさんだ処理の挙動を確認したいことが、しばしばあります。
例えば、特定の条件下で起動時にハングアップしないかや、再起動後に種々のレジスタが正しく設定されているかをチェックする場合などです。

こういった場合、既存のテストフレームワークでは、テストを書くのが難しいことが多いです。
例えば、実際には動きませんが、 Python の unittest なら、下記のように書けると嬉しいです。

意図としては、(*1) でレジスタ値を読み、(*2) で再起動し、 (*3) で再度レジスタ値を読む、ということを実現しようとしています。
(*1)reg1 変数に格納した結果を、 (*2) のシステム再起動後の (*4) でも参照できるようにしたいところがポイントです。

pausable_unittest

上記のようにテストスクリプトを書けるようにしたのが、 pausable_unittest です。
下記のように、 pausable_unittest.TestCase を継承することで、再起動をはさんでもテストを続けることができます。

ただし現在のところ、 Stackless PythonPyPy でしか動作しません。
また、 (*1) でファイルなどを開いた状態で (*2) の reboot を呼ぶと、エラーで停止します。
これらは実装上の都合によるものです。後述するように pausable_unittest では、現在の状態を pickle を用いてシリアライズするので、シリアライズできないオブジェクト (ファイルなど) が変数に保持されていると、状態の保存に失敗します。

pausable_unittest の仕組み

Stackless Python と PyPy でやや異なりますが、 PyPy の continulet が基本になっていますので、そちらから説明します。

continulet

PyPy には、 continulet というオブジェクトがあります。

continulet は軽量スレッドの一種ですが、ユーザーが明示的に continulet の中断/切り替え操作をしないと、continulet (軽量スレッド) が切り替わることはありません。
この点で Ruby の Fiber とほぼ同じものです。

また、 continulet はシリアライズすることができます。
つまり、中断状態の continulet を、実行中の関数の変数の値などを含めて、すべてファイルに書き出すことができます。当然、ファイルに書き出した状態を、後で復旧させることもできます。

この機能を用いて、下記のようなことが実現できます。

このスクリプトは以下のように実行されます。

  1. func 関数を実行する continulet を作成します。
    この時点では、まだ func は実行されません。
  2. c1.switch() を呼び、 func に制御を移します。
    その結果、 func が実行されます。
    func の中で con.switch() が呼ばれると func の制御を中断し、制御を main に戻します。
  3. 続いて、 pickle で中断状態を含めて continulet をシリアライズします。
  4. シリアライズされた文字列から、 continulet を復旧させています。
  5. 復旧させた continulet に対して switch を呼び、 func に制御を移します。
    このとき、前回に中断した状態から実行が再開されます。今回の例では、 for 文の中から再開されます。
    ローカル変数 i の値も含めて、前回に中断した状態から再開されます。

tasklet

一方で、 Stackless Python には似たような仕組みとして tasklet があります。
PyPy の continulet とややインターフェースは異なりますが、中断状態の tasklet を保存できるなど、ほぼ同じような特徴を持っています。

pausable_unittest では、 tasklet を continulet 風に使うためのラッパーオブジェクト pausable_unittest.continulet を用意して、 PyPy でも Stackless Python でも同じように動作させられるようにしています。
ただし、あくまで pausable_unittest で使うためだけのものなので、複数の continulet がある場合などをエミュレートするようにはできていません。

この、「中断状態をシリアライズし、後で復旧させられる」という特徴を活用することで、 pausable_unittest では楽に再起動後をまたいだ処理を書くことができます。

余談ですが、私は Ruby が好きなので、 Ruby の Fiber に同様の機能があれば、おそらく Ruby で実装していただろうと思います。

次回は、これらを使ってどのように pausable_unittest を実装したかについて書く予定です。

という予定だったのですが、 PyCon JP 2016 で発表させていただくことになったので、それが終わったらまとめようと思います。

2016年4月3日日曜日

Stackless Python と 1 バイナリの python.exe

Stackless Python と 1 バイナリの python.exe

仕事で Stackless Python を 1 バイナリの exe にして使ってみようと思ったので、少し整理してみました。
Python 2.7.9 に基づいたものですが、1 バイナリにしたものを python.exe からダウンロードできるようにしてあります。

また、これまで割と Ruby を使うことが多かったのですが、 Stackless Python のとある特長を活用したかったので、勉強してみました。

Stackless Python

Python には様々な亜種がありますが、その中の一つ Stackless PythonPyPy は、「tasklet や continulet を pickle できる」という大きな特徴があります。
他のプログラミング言語で言うと、 JavaScript のジェネレータや Ruby の Fiber を、保存可能な形式にシリアライズできる、ということです。

これができるということは、スクリプトを途中まで実行して中断し、後からその続きを実行する、ということが実現できます。
これが実現したかったので、今回は Stackless Python を使ってみました。

この処理は、オリジナルの Python では実現できませんが、 Stackless Python では少し Python インタプリタを変更することで、これを実現しています。

また、オリジナルの Ruby でも、実現ができません。
実装上の種々の理由がありますが、 Ruby では例外の捕捉と生成に setjmplongjmp を使っているので、なかなか難しいだろうと思います。

1 バイナリの python.exe と embeddedimport

利便性から、この Stackless Python を、できれば 1 バイナリにしておこうと思いました。
これは、python.exe hoge.py のように python.exe のみがあればスクリプトを実行できた方が、私の環境では都合がよかったからです。

そのため、 Stackless Python をスタティックビルドするとともに、既存の標準添付ライブラリの .py ファイルを、すべて python.exe の中に組み込むことにしました。

embeddedimport という名前のライブラリが、これを実現しています。
原理的には、標準ライブラリの zipimport と似たような仕組みで、 python.exe の中に静的に持っている .py ファイルの内容をロードしています。

このようにして得られた python.exe を BitBucket の python.exe に置いておきました。
また、 embeddedimport については、別途、時間があればまとめようと思います。

近頃、忙しいのでなかなかプログラミングができません。