読者です 読者をやめる 読者になる 読者になる

execとローカル変数

先日の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使いであるという証左とも言えるわけなのだが、本当は心から反省しているので勘弁していただきたいのである。