クラスブロックのひみつ
さて、 リスト内包のひみつ - atsuoishimoto's diary で、Python3では、リスト内包式は関数呼び出しとなることを説明した。
>>> a = [i*2 for i in range(3)]
というスクリプトは、次のように展開される。
>>> def _listcomp(_it): ... ret = [] ... for i in it: ... ret.append(i*2) ... return ret ... >>> _it = range(3) >>> a = _listcomp(it)
通常、この点はあまり気にする必要はないが、問題となるケースもなくはない。
クラスブロックのリスト内包
クラスブロックで次の処理を実行してみよう。
class Foo: NUMS = [i*2 for i in range(3)]
まあ、これは当然動作する。Foo.NUMS
の値は、[0,2,4]
となる。
では、これをちょっと直してみよう。
class Foo: N = 3 NUMS = [i*2 for i in range(N)]
これも問題ない。Foo.NUMS
の値は、同じく [0,2,4]
となる。
もうちょっと変えてみよう。
class Foo: N = 3 F = 2 NUMS = [i*F for i in range(N)]
一見、これも問題なさそうだが、実行するとエラーとなってしまう。
>>> class Foo: ... N = 3 ... F = 2 ... NUMS = [i*F for i in range(N)] ... Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in Foo File "<stdin>", line 4, in <listcomp> NameError: name 'F' is not defined
なんで N
は良くて、 F
はダメなのだろう。
Pythonの名前解決ルール
Pythonが spam*2
のような式の値を計算する時、Pythonは変数 spam
の値を、次の順序で検索する。
例えば、
def foo(): return 2 * spam
という関数を実行すると、Pythonは変数 spam
を
- 式を実行している
foo()
のローカル変数。 foo()
が所属するモジュールのグローバル変数。__builtins__
。
の順番で検索し、見つからなければ NameError
例外が発生する。
この名前の解決ルールは、Pythonプログラミングにおいて非常に重要なルールだ。それほど難しいルールではないので、確実に頭に叩き込んでおこう。
Pythonのプログラムを読んでいて、どこで定義されているかわからない変数や関数などがあっても、「ローカル変数かグローバル変数か __builtins__
のいずれかに必ず存在する」 ということも覚えておくと良いだろう。
クラスブロック
Pythonの名前解決ルールを頭に叩き込んだら、次のコードを見てみよう。
class Foo: N = 3 M = 2*N
当然ながら、Foo.M
の値は 2*3=6
となる。2*N
という式は N
という変数を参照しているが、Pythonは同じブロックで定義されている変数 N
を見つけ、値を取得する。
では、このコードをちょっと修正しよう。
class Foo: N = 3 def f(): return 2*N M = f()
このコードを実行すると、次のようなエラーが発生する。
>>> class Foo: ... N = 3 ... def f(): ... return 2*N ... M = f() ... Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in Foo File "<stdin>", line 4, in f NameError: name 'N' is not defined
関数 f()
内で、変数 N
が見つからないと言っている。
しかし、
N = 3 def f(): return 2*N M = f()
この処理は、どう見ても問題のないコードだ。実際、クラス定義以外で実行すると、正常に動作する。
次の例では、関数 f()
の式 2*N
は、グローバル変数 N
を参照して値を評価する。
>>> N = 3 >>> def f(): ... return 2*N ... >>> M = f() >>> M 6
また、次の例では、入れ子の関数 f()
は、親関数 bar()
のローカル変数 N
を参照する。
>>> def bar(): ... N = 3 ... def f(): ... return N*2 ... print(f()) ... >>> bar() 6
では、さきほどの例をもう一度見てみよう。
>>> class Foo: ... N = 3 ... def f(): ... return 2*N ... M = f() ...
Foo.f()
は変数 N
を参照しているが、この N
はどこで見つかるだろうか?
関数 Foo.f()
のローカル変数ではない。また、Foo.f()
は入れ子の関数ではないので、親となる関数も存在しない。
Foo.f()
のモジュールにも、N
は存在しない。
__builtins__
モジュールにも、当然 N
は存在しない。
「でも、Foo.N
があるじゃないか!」と言うかもしれない。しかし、
Foo.N
は、Foo.f()
のローカル変数ではなくFoo.N
は、Foo.f()
のモジュールのグローバル変数ではなくFoo.N
は、__builtins__
モジュールの値でもない
つまり、Pythonの名前解決ルールのどの項目でも、Foo.f()
では、 N
という名前を解決することはできないのである。
リスト内包
さて、冒頭のコードをもう一度見てみよう。
class Foo: N = 3 F = 2 NUMS = [i*F for i in range(N)]
このコードは、次のように実行される。
class Foo: N = 3 F = 2 def _f(_iter): ret = [] for i in _iter: ret.append(i*F) _iter = range(N) NUMS = _f(_iter)
リスト内包式が関数に変換されるのは、リスト内包式の []
内のすべてではなく、in
以下のイテレータを指定する式は関数内では実行されない。
したがって、
class Foo: N = 3 F = 2 NUMS = [i*F for i in range(N)]
の、 range(N)
の部分は、そのままクラスブロックで実行されるため NameError
とはならない。
しかし、i*F
の部分は関数内で実行されるため、クラス変数 F
を参照できず、NameError
とはなってしまうのである。
これはリスト内包だけではなく、ジェネレータ式や辞書内包、集合内包でも同様にエラーとなる。
対処方法
実のところ、クラスブロックに直接リスト内包を記述する場合は、うまい回避方法は思いつかない。
リスト内包を避けて通常のループを使用するか、または
class Foo: N = 3 F = 2 def _init(N, F): return [i*F for i in range(N)] NUMS = _init(N, F)
のように、関数でラップするのが無難だろう。
まとめ
- クラスブロックの中に定義した関数は、クラスブロックの変数を参照できない。
- クラスブロック内のリスト内包などでクラス変数を参照すると、エラーとなる場合がある。ならない場合もある。
- 対処方法: うーん。。。