魅力的なPython: 実行時に再ロードを行なう

長時間実行プロセスからモジュールを動的に再ロードする

Comments

この記事のシナリオを考えましょう。ローカル・マシンでプロセスを実行したいのですが、プログラム・ロジックの一部は他の場所で実行されているとします。特に、このプログラム・ロジックは時々更新されるので、プロセスを実行する場合には、最新のプログラム・ロジックを使用したいとします。この要件を満たすアプローチはたくさんあります。この記事では、それらのアプローチのいくつかを説明していきます。

この「魅力的なPython」コラムの連載では、パブリック・ドメイン・ユーティリティーTxt2Htmlの拡張について説明してきました。これは、「簡単なASCII」テキスト・ファイルをHTMLに変換するユーティリティーです。前回までの記事では、ユーティリティーのWebプロキシー・バージョンと、ユーティリティーのcurses インターフェースについて説明しました。わたしは、一部のASCIIマークアップをもっと便利な方法で変換できることに気付くことがあったり、特定のマークアップ構成を処理しながらバグを修正したりすることもあります。

実際、このコラムのわたしの記事は、ASCIIで作成したもので、皆さんが読まれているHTML形式に変換するのは編集プロセス時です。記事のドラフトを送信して発行する前に、次のプロセスのようなことを実行しています。

コマンドラインからの記事のHTML化
txt2html charming_python_7.txt > charming_python_7.html

必要に応じて、操作を変更するためのフラグを指定することができます。しかし、いずれにしても、わたしにとって重要なのは、コンバーターの最新バージョンがローカル・ドライブとパス上にあるということです。別のマシンで作業している場合や、このユーティリティーを使用したいユーザーにとっては、このプロセスは非常に面倒です。わたしのWebサイトをチェックし、バージョン番号とファイルの日付を比較し (変更が小さすぎるので番号を付けない場合もあります)、現在のバージョンをダウンロードして、その現在のバージョンを適正なディレクトリーにコピーし、コマンドラインからコンバーターを実行します (最後の参考文献 を参照してください)。

上記のプロセスでは、いくつかの手作業でかなり時間のかかるステップを伴います。もっと簡単にすべきであり、また簡単にすることができます。

コマンドラインからのWebアクセス

ほとんどの人はWebのことをGUI環境で対話形式によってページをブラウズする方法だと思っています。そうすることは、もちろんすばらしいことですが、コマンドラインにも多くの機能があります。テキスト・モードのWebブラウザーlynxを持つシステムは、主としてWeb全体を単なる別のファイル・セットとして扱い、コマンドラインツールを利用することができます。たとえば、次のような役立つコマンドがあります。

lynxでのコマンドラインからのWebブラウズ
lynx -dump http://gnosis.cx/publish/.
lynx -dump http://ibm.com/developer/. > ibm_developer.txt
lynx -dump http://gnosis.cx/publish | wc | sed "s/( *[0-9]* *\)\([0-9]*\)\(.*\)/\2/g"

1行目で表示されるのは、"Display David Mertz' home page to the console (as ASCII text)" (David Mertzのホームページをコンソールに表示する) (ASCIIテキストで) です。2行目で表示されるのは、"Save an ASCII version of IBM's current developerWorks home page to a file" (IBMの最新のdeveloperWorksホームページをファイルに格納する) です。3行目で表示されるのは、"Display the number of words in David's home page" (Davidのホームページにあるワード数を表示する) です (パイプで連結しているコマンドライン・ツールを示しているだけなので、細かいことは気にしないでください)。

lynxについて言えることは、(-dump オプションを指定すると) Txt2Htmlとほぼ正反対のことを実行する、ということです。lynxはHTMLをテキストに変換し、Txt2HtmlはテキストをHTMLに変換します。しかし、Txt2Htmlをlynxと同じ方法で使用することはできないという理由はありません。簡単なPythonスクリプトを使用すれば、lynxと同じように使用することができます。

'fetch_txt2html.py' コマンドラインからのコンバーター
import sys
from urllib import urlopen, urlencode
if len(sys.argv) == 2:
cgi = 'http://gnosis.cx/cgi/txt2html.cgi'
opts = urlencode({'source':sys.argv[1], 'proxy':'NONE'})
print urlopen(cgi, opts).read()
else:
print "Please specify URL for Txt2Html conversion"

このスクリプトを実行するには、次のように指定するだけです。

python fetch_txt2html.py http://gnosis.cx/publish/programming/charming_python_7.txt

この場合、ローカルのTxt2Htmlプロセスのすべてのスイッチが提供されるわけではありませんが、必要に応じてこれらのスイッチを追加することは簡単です。コマンドライン・ツールを使った場合と同じように、出力をパイピングしてリダイレクトすることができます。ただし、前述のバージョンで処理できるのはURLでアクセスできるデータ・ファイルだけで、ローカル・ファイルは処理できません。

実際、fetch_txt2html.py は、lynxが実行しないこと (そして、Txt2Htmlが自分自身でも実行しないこと) を実行します。URLからデータ・ソースを取り出すだけでなく、リモートでプログラム・ロジックを取得します。fetch_txt2html.py を使用する場合は、ローカル・マシンにTxt2Htmlがある ことさえ必要ありません。プロセスはリモートで (最新バージョンが) 呼び出され、その結果は、ローカル・プロセスを実行したかのようにユーザーに送り戻されます。何ともすばらしいではありませんか。Txt2Htmlのローカル・バージョンは、ローカル・ファイルと同じようにリモートのURLにアクセスできますが、自分自身が最新バージョンであることを確認することは、まだ行えません。

動的な初期化

fetch_txt2html.py を使用すると、変換時に最新のプログラム・ロジックが必ず使用されます。しかし、このアプローチでは、プロセッサー (およびメモリー) 要件がgnosis.cx Webサーバーに移行することにもなります。この特定のプロセスによって課せられる負荷は、特に高いわけではありませんが、クライアント上の処理がもっと効率良く、好ましいものになる他のタイプのプロセスを考えつくことは簡単です。

Txt2Htmlを編成する方法、つまりほとんどのプログラムを編成する方法とは、各種のユーティリティー関数でサポートされるいくつかの中核のフロー制御関数を使用することです。特に、ユーティリティー関数は、非常に頻繁に更新されている関数です。中核の関数 (main() など) は、大規模な変更を行う場合しか使用されません。簡単に言えば、プログラムを実行するたびに役に立つように更新できるものが、ユーティリティー関数です。実際、大抵の場合、関数の大半はメインのTxt2HtmlモジュールdmTxt2Html 内で問題ありません。

'd2h_textfuncs.py' 動的なTxt2Html更新
#-- Functions to massage blocks by type
#defTitleify(block):
#defAuthorify(block):
# ... [more block massaging functions] ...
#-- Utility functions for text transformation
#defAdjustCaps(txt):
#defcapwords(txt):
#defURLify(txt):
def Typographify(txt):
# [module] names
r = re.compile(r"""([\(\s'/">]|^)\[(.*?)\]([<\s\.\),:;'"?!/-])""", re.M | re.S)
txt = r.sub('\\1<em><code>\\2</code></em>\\3',txt)
# *strongly emphasize* words
r = re.compile(r"""([\(\s'/"]|^)\*(.*?)\*([\s\.\),:;'"?!/-])""", re.M | re.S)
txt = r.sub('\\1<strong>\\2</strong>\\3', txt)
# ... [more text massaging] ...
return txt
# ... [more text transformation functions] .....

最新の一番優れた形のサポート・モジュールを利用するには、いくつかの準備ステップが必要です。まず、ローカル・システムにメインのTxt2Htmlモジュールをダウンロードします (これは、1回限りのステップです)。次に、ローカル・システム上に次のようなPythonスクリプトを作成します。

'dyn_txt2html.py' コマンドラインからのコンバーター
from urllibimport urlopen
import sys
# Check for updated functions (fail gracefully if not fetchable)
try:
updates = urlopen('http://gnosis.cx/download/t2h_textfuncs.py').read()
fh = open('t2h_textfuncs.py','w')
fh.write(updates)
fh.close()
except:
sys.stderr.write('Cannot currently download Txt2Html updates')
# Import the updated functions (if available)
try:
from t2h_textfuncsimport *
except:
sys.stderr.write('Cannot import the updated Txt2Html functions')
# Set options based on runmode (shell vs. CGI)
if len(sys.argv) >= 2:
cfg_dict = ParseArgs(sys.argv[1:])
main(cfg_dict)
else:
print"Please specify URL (and options) for Txt2Html conversion"

dyn_txt2html.py スクリプトで、from t2h_textfuncs import * ステートメントが実行されると、事前にdmTxt2Html で定義されている (Typographify() のような) すべての関数が、t2h_textfuncs バージョンの同名関数に置き換えられることに注意してください。もちろん、t2h_textfuncs 内の関数が、単なるコメント付けされたプレースホルダーである場合は、置換は発生しません。

システムが異なると、STDERRへの書き込みを処理する方法も異なる、という小さい問題もあります。UNIXベースのシステムでは、スクリプトを実行するときにSTDERRをリダイレクトできます。ただし、現在のOS/2シェル環境や、Windows/DOS環境では、STDERRメッセージはコンソール出力に付加されます。前述のエラー / 警告は、ログ・ファイルに書き込んでしまうか、または、STDOUTをファイルに転送する習慣を付けてしまう (この方法のほうが有効であると考えられる場合) 必要があります。たとえば、次のようになります。

'dyn_txt2html' の コマンドライン・セッション
G:\txt2html> python dyn_txt2html.py test.txt > test.html
Cannot currently download Txt2Html updates

エラーはコンソールに出力されます。変換後の出力はファイルに転送されます。

dyn_txt2html.py はどうしてサポート・モジュールだけでなく、dmTxt2Html モジュール全体をダウンロードしないのか、という興味深い問題があります。ここでは、いくつかのことが進行しています。t2h_textfuncs サポート・モジュールは、特に関数の大半が除外され / コメント付けされているので、メインのdmTxt2Html モジュールよりもかなり小さくなっています。モデム接続では、これは著しい高速化を意味します。しかし、ダウンロード・サイズが本題ではありません。

Txt2Htmlでは、ユーザーが最新モジュール全体を自動ダウンロードするかどうかは、おそらく関係ありません。しかし、プログラム・ロジックが分散される 、特にメインテナンスの責任が分散されるシステムではどうでしょうか。Alice、Bob、Charlieが モジュールFuncs_AFuncs_BFuncs_C をそれぞれ担当するとしましょう。彼らはそれぞれ、制御下で定期的に (そして個々に) 関数に変更を加え、最新で一番優れた関数を独自のWebサイト (たとえば、http://alice.com/Funcs_A.py) にアップロードします。この場合、3人のプログラマー全員に同一のメイン・モジュールに変更を加えさせることは不可能です。しかし、dyn_txt2html.py のようなスクリプトを、Funcs_AFuncs_BFuncs_C のすべてを起動時にインポートするように拡張することは可能です (そして、これらのリソースを取得できない場合にはMainProg バージョンに戻します)。

長時間実行の動的プロセス

これまでわたしたちが見てきたツールは、初期化時に、更新済みのリソースをダウンロードすることで、動的なプログラム・ロジックを獲得します。これは、コマンドライン処理やバッチ処理では大いに意味がありますが、長時間実行アプリケーションではどうでしょうか。多くの場合、このような長時間実行アプリケーションは、間断なく応答することが必要なサーバー・プロセスです。ただし、この説明の中では、前回の記事 のために開発したcurses_txt2html.py スクリプトを使用して、Pythonのreload() 関数を示します。プログラムcurses_txt2html は、dmTxt2Html のローカル・コピーのラッパーです。ここではcurses プログラミングについては、2度目の紹介をしなくても、curses_txt2html が複数の順次Txt2Html変換を構成して実行するための対話式メニューを提供するもの、と言うだけで十分でしょう。

curses_txt2html は、常にバックグラウンドで実行中の状態に置かれる可能性があるので、セッションに切り替え、変換を実行する場合には、最新のプログラム・ロジックが利用できるようになっていなければなりません。この特定の簡単な例では、アプリケーションを閉じてから再起動することは、正直なところ難しいことではないので、特に困ったことは起こりません。しかし、常に実行中の状態であることに強く依存している他のプロセス (おそらく、セッション内で実行されるアクションを示すプロセス) があるということは、容易に想像できます。

この記事では、新しいFile/Updateサブメニューを追加しました。起動すると、単にupdate_txt2html() という新しい関数が呼び出されます。何が発生したかの確認を提供することに関連するcurses 呼び出しは別として、これらのステップについては、すでにこの記事の他の例で紹介しました。

'curses_txt2html.py' 動的更新関数
def update_txt2html():
# Check for updated functions (fail gracefully if not fetchable)
s = curses.newwin(6, 60, 4, 5)
s.box()
s.addstr(1, 2,"* PRESS ANY KEY TO CONTINUE *", curses.A_BOLD)
s.addstr(3, 2,"...downloading...")
s.refresh()
try:
from urllibimport urlopen
updates = urlopen('http://gnosis.cx/download/dmTxt2Html.py').read()
fh = open('dmTxt2Html.py','w')
fh.write(updates)
fh.close()
s.addstr(3, 2,"Module [dmTxt2Html] downloaded to current directory")
except:
s.addstr(3, 2,"Download of updated [dmTxt2Html] module failed!")
reload(dmTxt2Html)
s.addstr(4, 2,"Module [dmTxt2Html] reloaded from current directory  ")
s.refresh()
c = s.getch()
s.erase()

dyn_txthtml.py 関数とこのupdate_txt2html() 関数には、2つの重要な違いがあります。1つは、先に進んで、単なるサポート関数ではなく、メインのdmTxt2Html モジュールをインポートすることです。これは、主にインポートを簡易化するだけのことです。ここでの問題は、import dmTxt2Html を使用して、from dmTxt2Html import * の代わりにモジュールにアクセスすることです。たいていの場合、これは安全なプロシージャーですが、結果的にdmTxt2Html 内の関数を上書きすることは (偶然にしろ、故意にしろ) 難しくなります。d2h_textfuncs から関数を接続したい場合は、インポートしたサポート・モジュール上でdir() を実行し、属性の形で "dmTxt2Html" ネーム・スペースにメンバーを接続しなければなりません。このスタイルの上書きの実行は、ユーザーの練習用に残してあります。

update_txt2html() 関数によって生じる一番重要な違いは、Pythonの組み込み関数reload() の使用です。真新しいimport dmTxt2Html を実行するだけでは、前にインポートした関数は上書きされません。注意してください。初心者の多くは、モジュールを再インポートすると、メモリー内のバージョンも更新されるだろうと思っています。メモリー内のバージョンは更新されません。モジュール内の関数のメモリー内のイメージを更新するには、reload() を使用してモジュールを再ロードします。

上記では、もう1つ小さな仕掛けが実行されます。更新されたdmTxt2Html モジュールのダウンロード場所は、そのディレクトリーがdmTxt2Html の最初のロード元のディレクトリーかどうかに係らず、ローカルの作業ディレクトリーになります。実際、そのディレクトリーがPythonのライブラリー・ディレクトリー内にある場合には、おそらくそこで作業することはないでしょう (おそらく、ユーザーとして、そのディレクトリーに対するアクセス権がありません)。しかし、reload() 呼び出しは、まず現行のディレクトリーからロードすることを試みてから、次に Pythonのパスの残りからロードを試みます。そのため、ダウンロードが成功するかどうかに係らず、reload() は安全な操作です (ただし、新しいモジュールをロードできるかどうかは分かりません)。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=239851
ArticleTitle=魅力的なPython: 実行時に再ロードを行なう
publish-date=11012000