先日のPyCon JPで、私はまたもやってしまったのである。Hands on の講師という大役を引き受けておきながら、間違った演習用のスクリプトを書いてしまった。昔からの思い込みを元に、大雑把な実験だけで書いてしまったのである。
何を間違えたかというと、Pythonのローカル名前空間の扱いだ。 昔から知っているはずなのに忘れ、http://d.hatena.ne.jp/atsuoishimoto/20101006/1286338675 でも間違えて pokarim さんにご指摘いただき、今回また間違えたんだからタチが悪い。ここはきっちりブログエントリを書いてもう「うっかり」とならないようにしよう。
問題はどんなのかというと、煎じ詰めればこのコードだ。
def spam(): locals()['ret'] = 'ham' return ret print spam()
ローカル名前空間の辞書を取得し、直接変数を登録している。これを実行すると、
>>> print spam() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in spam NameError: global name 'ret' is not defined
とエラーになる。この処理、昔はエラーとならずに ham
が返ってきたのだが、ローカル変数アクセスの最適化処理のためにエラーになるように変更されたのだ。
では、代入文を使わずにローカル変数を作る手段というのはないのだろうか? クロージャを含まない関数なら、exec
文を使ってローカル変数を作成できる。
def spam2(): exec "ret=100" return ret
http://d.hatena.ne.jp/atsuoishimoto/20101006/1286338675 に書いたとおり、クロージャを含む関数ではエラーとなって実行できない。
ところでこの話はもうちょっと複雑で、こんなコードがあったとする。
def spam(): exec "" locals()['ret'] = 200 return ret
最初の例との違いは、空の、何もしない exec
文があるかどうかだけだが、この例では実はエラーなしで動いてしまう。実は、exec
文がある関数ではローカル変数の最適化が発生しないため、ローカル名前空間を直接編集しても動いてしまうのだ。
ついでに書いておくと、ローカル名前空間を直接編集できるケースに、関数内でのfrom xxxx import *
構文を使用している場合がある。
def spam2(): from sys import * locals()['ret'] = 300 return ret print spam2()
このコードも警告が出力されるものの、正常に 300
が返ってくる。
というわけで、今回の私のミスは、exec
文を使えば文字列でローカル変数を更新できるが、それは実際に exec
を実行している関数内だけで有効なテクニックであって、profile.runctx()
のような、別関数に文字列を渡して実行させるケースでは役に立たないというのをテスト中に見逃していた点にある。
言い訳をさせてもらえば、私はPythonのこの挙動を、ローカル変数の最適化が行われた時に調べてちゃんと知っていたが、しかしそれはもう10年も前の話だ。また、Python初心者はPythonの動的な特徴を濫用して locals()
だのなんだのを使った「賢い」コードを書きたがるが、熟練者はそんなマジカルな処理には手を出さないもので、普段は全く意識しない。したがって、私がこの仕様を忘れていたということは、すなわち私が練達のPython使いであるという証左とも言えるわけなのだが、本当は心から反省しているので勘弁していただきたいのである。