subprocessの互換性向上

subprocessは少し問題あり

tags:tips
created:2008-03-05T20:24:24

subprocessモジュールは既存のpopenシリーズよりも クロスプラットフォーム互換を狙ったものですが、 まだ少し問題が残っています。

現状見つかっている問題点

ユニコードで楽チンとはいかない

引数リストを渡す仕様ですが、 すべての要素がプラットフォーム依存のstr型に変更済みであることを期待しています。

unicodeインスタンスを含む場合、 あらかじめ「sys.getfilesystemencoding()」でエンコードしましょう。

自然終了しないプロセスをkillできない

扱う外部アプリケーションによっては強制的に終了させたい時があるのですが、 そのあたりのサポートがなく、プラットフォームごとに違う方法でkillしなければいけません。 当然タイムアウト処理なんかもサポートしていません。

パス名にダブルクオーテーションが含まれるとトラブる

基本的にシェルベースでしかダブルクオーテーションは必要ないので、 引数リストという渡し方でダブルクオーテーションが無用なのは理解できます。 しかし、環境変数「PATH」の中ではどうでしょう? UNIX文化では、空白を含むようなパス名を見たことがないので 問題が露呈していないだけかもしれませんが、 Windowsでは当たり前のように空白を含むパス名が使われています。

そして、環境変数「PATH」にはダブルクオーテーションで 囲ったパス名とそうでないパス名の混在がありうるのです。

通常のWindowsシェル「cmd.exe」では問題なく実行ファイルを検索してくれますが、 subprocessではダブルクオーテーションで囲ったパスは検索してくれません。

暫定的な解決方法として、subprocessを使うモジュールの先頭でこんなことしてます。

os.environ[‘Path’] = ‘;’.join([i.strip(‘”’) for i in os.environ[‘Path’].split(‘;’)])

パイプのリードはサイズに気をつけよう

子プロセスの標準入出力のリダイレクトには注意が必要です。 リーダブルパイプオブジェクトである「stdout」や「stdin」の読み方には注意が必要です。 パイプ内部のキューに蓄積していない分まで読もうとすると、 子プロセスが出力するまで待ち状態になります。 子プロセスが終了すれば、この待ち状態は中断されますが、 子プロセスが動き続ける上になにも出力しない場合、 2度とこの待ち状態は終了しません。

プロセスが終了するものでも、stdinとの兼ね合いでデッドロックすることもあるようです。 面倒であれば、subprocess.Popenオブジェクトのcommunicateメソッドを使いましょう。 こちらは内部で「stdout」や「stdin」の読み出しスレッドを個別に走らせます。 デッドロックに陥る事はありません。

「stdout」や「stdin」の読み出しにはスレッドを使うのが一般的のようです。 継続的に動作する子プロセスを扱う場合、 強制的にとめる手段とともにスレッドによる監視が必要になるでしょう。

まとめ

プロセスやファイルロックがらみに関してはは、 若干プラットフォームの差が残っていますね。

注意深く原因を追えば対処できる内容だとは思いますが、 これだと、subprocessモジュールの存在意義が薄れてしまっているような気がします。

これらをカバーできるようなモジュールのひとつはPyPIに公開されています。

こちらがPIPEを正しく扱うバージョン: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554

こちらが、キルできるバージョン: http://benjamin.smedbergs.us/blog/2006-12-11/killableprocesspy/

こちらが、タイムアウト機能付バージョン: http://trentm.com/projects/process/

パス名の取り扱いを直したものと融合してパッチでもつくろうかしら・・・。