このBlogは移転しました。今後は aish.dev を御覧ください。

クラスブロックのひみつ

さて、 リスト内包のひみつ - 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の名前解決ルール

Pythonspam*2 のような式の値を計算する時、Pythonは変数 spam の値を、次の順序で検索する。

  1. 式のスコープ。関数内で定義された入れ子の関数で実行していれば、親関数のスコープも検索する。

  2. 式のモジュールのグローバル変数

  3. 組み込み関数のモジュール __builtins__

例えば、

def foo():
    return 2 * spam

という関数を実行すると、Pythonは変数 spam

  1. 式を実行している foo() のローカル変数。
  2. foo() が所属するモジュールのグローバル変数
  3. __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)

のように、関数でラップするのが無難だろう。

まとめ

  • クラスブロックの中に定義した関数は、クラスブロックの変数を参照できない。
  • クラスブロック内のリスト内包などでクラス変数を参照すると、エラーとなる場合がある。ならない場合もある。
  • 対処方法: うーん。。。