SyntaxHighlighter

2018年11月24日土曜日

1バイナリの Python と exepy

1バイナリの Python と exepy

1バイナリにした Stackless Python の更新もそろそろ終わりなので、その中に含まれている exepy ライブラリについてまとめておこうと思います。

1バイナリの Python の仕組み

この Python は、 外部 DLL や *.py ファイルが不要になっています。
DLL については、ビルド時に tcl/tk, OpenSSL などを含め、静的リンクすることで解決しています。
*.py ファイルについては、 embeddedimport というモジュールを新規に開発することで対応しています。

これらについては Single Binary 版の Stackless Python 3.6.4Single Binary StacklessPython 3.6.4 L8 リリースに簡単にまとめてあります。

exepy の仕組み

2.0.0 以降の exepy では、以下のようにして exe を作ることができます。

上記で作られた exe は、一時フォルダにファイルを展開したりすることなく、単一の exe ファイルだけで input.py の処理を実行することができます。

さて、このとき、 sample.exe は python.exe とほぼ同じバイナリになっています。
唯一の違いは、 input.py に相当する内容が sample.exe のリソースとして組み込まれているところです。

embeddedimport は、実行している exe ファイル (sample.exe) のリソースに特定の ID のリソースが含まれている場合、その内容から *.py のデータをデコードしロードします。
つまり、 import input が実行された場合、 embeddedimport は以下の順で *.py を探します。

  1. 現在実行中の exe ファイルのリソース内
  2. exe 内に静的に持っている *.py のデータ (具体的には embeddedimport_data.c の内容)
そのため、元々の python.exe にリソースを埋め込むだけで、 *.py を含んだ exe ファイルを作ることができます。

exepy のサンプル

いくつかサンプルを示しておきます。

複数ファイルからなるサンプル

main.py と sub.py があり、 main.py が sub.py を import しているとします。

上記のファイルを下記のように exe 化します。

上記で作られた sample.exe の実行は、実質的に下のコマンドを実行したときとほぼ同等の動きになります。

画像ファイルなどの抱き込み

Single Binary StacklessPython 3.6.6 L16 に含まれる exepy 2.1.0 では、画像ファイルなどの抱き込みができるようになっています。
例えば、 picture.png を tkinter で表示する例を考えます。

以下のような main.py を用意します。

こちらを以下のコマンドで exe 化します。

これで picture.png も組み込まれた exe ができあがります。

__loader__.get_data(...) で、通常のファイルのようにデータを取得することができます。
あとはこれを用いて tkinter の Label, Widget などで表示すればよいです。

今回の変更で embeddedimporter が get_data をサポートしました。
これにより、任意のバイナリデータを組み込めるようになっています。

あとは、アイコンの変更機能ぐらいを追加して開発も終了です。

2018年6月26日火曜日

Single Binary StacklessPython 3.6.4 L8 リリース

Single Binary StacklessPython 3.6.4 L8 リリース

業務の効率化や Python インタプリタの勉強も兼ねて、 StacklessPython を 1 バイナリ化していましたが、バージョン L8 まできました。

GitHub のリリースページからダウンロードできます。

今回は、主に以下の変更をしました。

  • tkinter のサポート
  • EXE 化の実験的サポート
  • Windows Subsystem 版のサポート

tkinter のサポート

これまでに Python の標準ライブラリは、ほぼすべてサポートしていましたが、 tkinter はいくつかの理由でサポートしていませんでした。

tkinter をサポートし、1 バイナリで実行するには、 tcl/tk のライブラリを静的にリンクする必要があります。
また、 tcl/tk は、いくつかの *.tcl ファイルを必要とするので、こちらについても対応する必要があります。

静的リンクについては、 nmake -f makefile.vc core OPTS=static のように OPTS として static を指定すれば、静的リンク版をビルドできるので、こちらを使うことになります。

一方で、*.tcl ファイルの抱き込みについては、やや面倒です。

*.tcl ファイルの抱き込み

tcl では、Tcl_Filesystem という構造体で、複数のファイルシステムを持つことができます。
これは一言で言うと Python の path_hooks に相当するものです。
今回は embeddedFilesystem というファイルシステムを作成し、 embeddedfs:/tcl8.6/init.tcl のように embeddedfs:/ で始まる仮想的なパスが指定された場合に、バイナリの中に保持しているファイルの中身を返すようにしています。

詳しくは tclEmbeddedFilesystem.c を参照してください。

EXE 化の実験的サポート

せっかくなので、おまけの機能として EXE 化の機能を付けてみました。
なお、元々、標準ライブラリ外の機能は考慮していないので、py2exe や PyInstaller の代わりには使えません。あくまで、標準ライブラリだけを使って書けるスクリプトの EXE 化ができます。

例えば下記のように、 tk でウィンドウを作成し、クリックでカウントアップするだけのプログラムを EXE 化してみます。

今回の Single Binary StacklessPython では、上記のように tkinter を使うような場合も、実行することができます。

これを、単一の EXE にするには、下記のようにします。
ただし、これはまだ実験的な機能なので、将来は仕様を変更するかもしれません。

上記を実行すると sample.exe が生成されます。

サイズは 14MB ぐらいになりますが、テンポラリディレクトリにファイルを展開したりはしないので、かなり高速に実行されます。

Windows Subsystem 版のサポート

今回から pythonw.exe のように、コンソール画面が表示されないバイナリを作っておきました。

こちらは、主に GUI を使うスクリプトを EXE 化する際に使うと便利です。

Python, tcl/tk の実装の勉強にもなり、 AppVeyor の利用の勉強にもなりましたが、すべての標準ライブラリを 1 バイナリに収めたので、そろそろ終了にする予定です。

2018年6月13日水曜日

Single Binary 版の Stackless Python 3.6.4

Single Binary 版の Stackless Python 3.6.4

Stackless Python が 3.6.4 対応されていたので、 1 バイナリ化した Stackless Python も対応してみました。

GitHub のページから、ダウンロードしてご利用ください。

1 バイナリ化した Stackless Python

以前に Stackless Python 2.7 を 1 バイナリ化しましたがその当時の変更を Python 3.6 に対応させることになります。
今回は、その際の対応と、そもそも 1 バイナリ化した Stackless Python はどういうものかについて、まとめようと思います。

1 バイナリ化した Stackless Python 作成の目的

業務の都合上、「種々の PC 実機上で簡単に Python を実行したい」という動機がありました。
それに加えて、 pausable unittest を実行したかったので、通常の Python ではなく Stackless Python を使うことにしました。

また、極力、通常の Python と比べて、実行速度が遅くならないようにしたいということも考えていました。
前に Ruby を OCRA で固めたときは、実行前の展開にやや時間がかかるのがストレスだったので、今回はそういったことがないようにしたいと思っていました。

1 バイナリ化した Stackless Python の変更点

デフォルトの Stackless Python から以下の変更を加えました。

  • プログラム引数の変更
  • embeddedimport の実装
  • 3rd party ライブラリの同梱
  • その他の小変更

プログラム引数の変更

1 バイナリ化した Stackless Python は、引数の意味を二つだけ変更してあります。

  • -B: この引数は意味が逆になっており、-B が存在しないときに、 *.pyc ファイルを作成しないようになっています。
  • -E: この引数も意味が逆になっており、-E が存在しないときに、環境変数を参照しないようになっています。

これらの変更は、いずれも未知の環境で動作しやすいようにしておくためです。
時刻が正しくない複数の実機で実行した場合や、他に Python の処理系がインストールされていた場合にも、正しく動作させるためです。

embeddedimport の実装

通常、 Python の import 文は、 sys.path に登録されたパスを順に探し、その中から外部モジュールをロードします。
また、 sys.path には python.zipも 登録されており、 ZIP ファイルの中もサーチの対象になっています。
この ZIP ファイルからのロードは、 zipimport というモジュールが担っています。

今回は、「一つのバイナリにしたい」という目標があったので、 zipimport を参考にして、 embeddedimport を作り、実装しています。

挙動の概要としては、以下のようになります。

  1. ビルド時に、 Lib 以下の *.py ファイルをすべて C 言語の文字列に変換して、 python のバイナリに組み込んでおきます。
  2. 実行時には、 sys.pathC:/path_to/python.exe のように python.exe のフルパスを登録しておきます。また、 sys.path_hooksembeddedimporter モジュールを登録しておきます。
  3. これにより、 import pickle などが処理される際に embeddedimporter によってモジュールのインポートが試みられます。
  4. embeddedimporter では、自身が保持している *.py ファイルの中から指定されたファイルを探し、それを実行します。

3rd party ライブラリの同梱

快適に使えるように、いくつかのライブラリを同梱してあります。

  • comtypes
  • pycodestyle
  • pyflakes
  • pyreadline
  • PyYAML with libyaml

PyYAML に関しては、 libyaml をビルドしておき、 C 言語のサポートがあった方がずっと速いので、そのようにしてみました。

一点、注意としては、 libyaml とのブリッジライブラリである _yaml は、 Cython 形式になっており、 C 言語としてビルドするには前処理が必要でした。
Stackless Python の場合、 Fast Calll 時の関数呼び出しの処理が変更されているので、 CYTHON_FAST_PYCALL を 0 に設定し、 _yaml から Fast Call させないようにすることが必要でした。

その他の小変更

プログラムの引数を変更した関係で、いくつかのテストを変更しました。 -E を使っているテストは非常に多かったので、その辺りを中心に変更しました。

また、 C 言語で書かれた拡張ライブラリ *.pyd を、静的に Python にリンクするために、ビルド方法や config.c でのビルトインモジュールの定義部分を変更しました。

私の業務の都合上、 XML の属性をソートしたくなかったので、 xml.etree.ElementTree の出力部分に少し手を加えました。
具体的には ElementTree.writesort_attrib=True というキーワード引数を追加し、ソートを制御できるようにしておきました。

これらの対応を行い、テストがほぼ通ったので、ついにリリースになりました。
Windpws PE 上での作業や、多数のテスト実機上で Python を走らせる必要がある場合などに、ご利用いただければと思います。

2018年3月8日木曜日

Windows 上で一時的に Python を使って作業する際に使っているバッチファイル

Windows 上で一時的に Python を使って作業する際に使っているバッチファイル

私は、仕事でクリーンな (OS 展開直後の) Windows 実機や Windows PE 上で、Python を用いて作業をすることがしばしばあります。
今回は、その際によく使っているスクリプトを備忘録としてまとめておきます。

いずれも、 Python の -x オプションを使って、バッチファイル先頭行の goto 文をスキップしています。

1バイナリの Python

前提として、1バイナリ化した Python を使います。

以前に書いたように、ほぼすべての標準ライブラリを含んだ StacklessPython を、単一の EXE ファイルをコピーするだけで動作するようにしてあります。
こういった用途のために作ったので、インストールが不要かつコピーするだけで使えるようにしました。

管理者権限で実行する Python スクリプト

下記のようなバッチファイルを用意し、print("hoge") 部分を好きな Python スクリプトに変更すれば、ダブルクリックするだけで、管理者権限の Python スクリプトとして実行することができます。
なお、同じディレクトリに python.exe が必要です。

readline を有効にした状態で Python インタプリタを立ち上げるスクリプト

こちらも、下記のようなバッチファイルを用意し、ダブルクリックすれば、 Python のインタプリタが立ち上がります。
pyreadline を使っているので、 Ctrl-f などのキーバインドや、補完、ヒストリ保存もできます。

作業の内容によっては使えると思いますので、置いておきます。

2017年12月16日土曜日

Outlook のキーワード強調表示用のフォントを作成する

Outlook のキーワード強調表示用のフォントを作成する

LigatureYourName

前回の Outlook でキーワードハイライトを実現する方法の続きです。

LigatureYourName というサイトでフォントを作成することができます。
せっかくなので、 Heroku で WEB サービスとして作成してみました。

フォントの情報

ベースフォント
ベースとなるフォントは、源真ゴシックです。
ライセンス
源真ゴシックに準じ、 SIL Open Font License 1.1 のもとに使用することができます。

その他

  • アルファベットなど、日本語以外の文字ではうまく強調表示されないことがあるようです。
    アプリケーションの描画方法に依存するようですが、理由をご存知でしたら教えてください。
  • 種類は、デフォルトで OpenType (PostScript) です。
    これは、 Windows10 の Creators Update より前では、TrueType 形式のフォントは汚く表示されることがあるためです。
    TrueType 形式が必要な場合、「詳細」欄で選択することができます。
  • このフォントの利用により生じたいかなる損害についても、一切責任を負いません。
    ご自身の責任の範囲でお使いください。

次回は、 LigatureYourName のサイトを作った際の技術的な内容について、まとめる予定です。

Outlook で本文中の固定キーワードを強調表示する方法

Outlook で本文中の固定キーワードを強調表示する方法

はじめに

Outlook は広く使われているメーラーの一つではないかと思いますが、なぜかキーワードハイライトができないようです。
例えば、自分の名前を本文中で常にハイライトしたいのですが、私が探した限り、そういった方法は見つかりませんでした。

以前に使ったことがある Becky!Thunderbird では標準機能やプラグインでサポートされていますので、不便に感じていました。

今回は Outlook で固定キーワードを強調表示する方法についてまとめます。

キーワードハイライトの仕組み

専用フォントによるハイライト

Outlook 用に、専用のフォントを入れ、そのフォントで表示することで実現します。
そのフォントでは、あらかじめ強調したいキーワードの文字だけを太字にしておくなどすることで、疑似的にキーワードハイライトすることができます。
例えば、「ハイライト」というキーワードを白黒反転で強調するような場合、以下のようになります。

このように、あらかじめ指定した「ハイライト」が指定した白黒反転で強調表示されています。
サンプルページで実際の挙動を確認できます。

リガチャ (合字) によるキーワード指定

前掲した例を見ると分かりますが、「ライト」は強調表示されていません。
これは、フォントの「リガチャ (合字)」機能を使って実現しているためです。

Wikipedia の合字ページを見ると分かりますが、リガチャとは欧文などで特定の並びの文字に専用のフォントを指定できるようにする機能です。
よく目にする例では、ff の並びに対し、二つの f がいい具合に合わさったものがあります。

このフォントの機能を活用し、日本語でも特定の並びの文字に対してリガチャを作成することで、疑似的なキーワードハイライトを実現することができます。
今回の例では、「ハイライト」を白黒反転したものを一つのグリフ (字形) として作成し、それを「ハイライト」の文字の並びに対して使うように、リガチャ指定しています。
こういった仕組みのため、部分文字列の「ライト」が強調表示されることはありません。

制限事項

この仕組みでキーワードハイライトを実現する場合、以下のような制限事項があります。

強調するキーワードを動的に変更できない
強調するキーワードを変更する場合、フォントを生成しなおす必要があるので、動的にキーワードを変更することはできません。
リガチャをサポートしている Outlook でのみ使える
具体的には Outlook 2010 以降が必要です。
(Outlook で使う場合) HTML メールの強調には使えない
テキストメールの読み書きに対してしか使えません。
改行がはさまるとリガチャにならない
改行が挟まった場合は、通常の文字として表示されます。

次回は、実際にフォントを作成する方法についてまとめます。

2017年10月27日金曜日

Nyagos の拡張 (主に Subversion 用 Lua スクリプト)

Nyagos の拡張 (主に Subversion 用 Lua スクリプト)

前回は Nyagos を tcsh 風に使うようにするための変更についてまとめました。
今回は、それに加えてもう少し Subversion まわりについて拡張してみました。

できること

作成した vcs_info.lua で、以下のことができます。

  • svn ls https://github.com/masamitsu-murase/nyagos.git/trunk/[Tab] のように、 svn で始まる場合に URL のパスに対しても補完することができます。
  • VcsInfo.box_svn_revisions(path) のように呼ぶことで、 C-x C-g の git のリビジョン選択のように、 Subversion のリビジョンを選択することができます。
  • VcsInfo.check_vcs_branch() で、現在のパスが svn, git, hg のいずれかを判別し、返してくれます。
    また、 git, hg の場合はブランチ名を、 svn の場合は親ディレクトリの名前を、二つ目の戻り値として返してくれます。

必要なファイルは nyagos.d/catalog/vcs_info.lua として GitHub に置いてあります。
このファイルを nyagos.d/catalog 以下に置き、 .nyagos でいくつか設定をします。

Subversion の URL の補完

この機能を使うには、 vcs_info.lua.nyagos で use するだけです。
.nyagos の例を見てください。

このスクリプトを使うと、svn で始まるときに限り、後続の URL を補完してくれます。

svn ls コマンドで、リポジトリのディレクトリを見ながら補完するので、環境によってはやや遅いですが、私としてはそれなりに快適に使えています。

また、デフォルトでは直前の補完結果を 10秒間はキャッシュしていますので、連続で Tab を押したときは軽快に動くようになっています。

Subversion のリビジョンの選択

コマンドラインでマージをする際に、マージ対象のリビジョンを手で入力するのは面倒です。
git の場合は、 C-x C-g でログを表示し、ハッシュ値を入力することができますが、これと同じようなことができることを目指しました。

この機能を使う場合、 box.lua のコードを参考に、以下のようなコードを .nyagos に追加する必要があります。

上記のように、 VcsInfo.find_svn_path で現在のコマンドライン入力の中から URL らしきものを探し、 VcsInfo.box_svn_revisions でリビジョンを選択することができます。

git, svn, hg のブランチ名の取得

VcsInfo.check_vcs_branch を呼ぶことで、現在のディレクトリのブランチ名を取得することができます。

以下のようなルールでブランチ名を取得しています。

git, hg
git rev-parse --abbrev-ref HEADhg branch でブランチ名を取得しています。
svn
現在のディレクトリを含むチェックアウトツリーのルートディレクトリを探し、その一つ上のディレクトリ名を取得します。
例えば、http://example.com/trunk をチェックアウトしたディレクトリ以下なら、trunk を返します。
同様に、http://example.com/branches/feature_a をチェックアウトしたディレクトリ以下なら、 feature_a を返します。

私は、この機能を用いて以下のようにプロンプトを表示しています。

このスクリプトだと、以下のようにプロンプトが表示されます。

もちろん、 Subversion のディレクトリ名が日本語を含む場合でも、適切に動作します。

Nyagos は Lua で拡張できるおかげで、割と楽に拡張することができました。