クラスのスコープとリスト、セットまたは辞書の内包表記、ジェネレータ式は混在しません。
理由; または、これに関する公式の言葉
Python 3 では、リスト内包表記には独自の適切なスコープ (ローカル名前空間) が与えられ、ローカル変数が周囲のスコープに流れ込むのを防ぎました (「内包表記のスコープの後でも名前を再バインドするリスト内包表記」を参照してください)。モジュールや関数でこのようなリスト内包表記を使用する場合は素晴らしいことですが、クラスでは、スコープは少し、うーん、奇妙です。
これはpep 227に記載されています:
クラス スコープ内の名前にはアクセスできません。名前は、最も内側の関数スコープで解決されます。クラス定義がネストされたスコープのチェーンで発生する場合、解決プロセスはクラス定義をスキップします。
およびclass
複合ステートメントのドキュメントで:
クラスのスイートは、新しく作成されたローカル名前空間と元のグローバル名前空間を使用して、新しい実行フレームで実行されます (名前付けとバインディングを参照)。(通常、スイートには関数定義のみが含まれます。) クラスのスイートの実行が終了すると、その実行フレームは破棄されますが、そのローカル名前空間は保存されます。[4]次に、基本クラスの継承リストと属性ディクショナリの保存されたローカル名前空間を使用して、クラス オブジェクトが作成されます。
鉱山を強調します。実行フレームは一時的なスコープです。
スコープはクラス オブジェクトの属性として再利用されるため、それを非ローカル スコープとして使用できるようにすると、未定義の動作が発生します。x
たとえば、ネストされたスコープ変数として参照されるクラス メソッドが同様に操作されるとどうなるでしょうFoo.x
か? さらに重要なことに、それは のサブクラスにとって何を意味するのFoo
でしょうか? Pythonは、関数スコープとは非常に異なるため、クラス スコープを別の方法で処理する必要があります。
最後になりましたが、実行モデルのドキュメントのリンクされた名前付けとバインディングのセクションでは、クラス スコープについて明示的に言及しています。
クラス ブロックで定義される名前のスコープは、クラス ブロックに限定されます。メソッドのコード ブロックには拡張されません。関数スコープを使用して実装されるため、これには内包表記とジェネレーター式が含まれます。これは、以下が失敗することを意味します。
class A:
a = 42
b = list(a + i for i in range(10))
つまり、要約すると、そのスコープに含まれる関数、リスト内包表記、またはジェネレータ式からクラス スコープにアクセスすることはできません。そのスコープが存在しないかのように動作します。Python 2 では、リスト内包表記はショートカットを使用して実装されていましたが、Python 3 では独自の関数スコープを取得したため (最初からあったはずです)、例が壊れています。他の内包表記タイプには、Python のバージョンに関係なく独自のスコープがあるため、set または dict 内包表記を使用した同様の例は、Python 2 では機能しません。
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
(小さな)例外。または、なぜ一部がまだ機能するのか
Python のバージョンに関係なく、周囲のスコープで実行される内包表記またはジェネレーター式の一部があります。それは、最も外側の iterable の式になります。あなたの例では、それは次のrange(1)
とおりです。
y = [x for i in range(1)]
# ^^^^^^^^
したがって、x
その式で使用してもエラーはスローされません。
# Runs fine
y = [i for i in range(x)]
これは最も外側の iterable にのみ適用されます。内包表記に複数の句がある場合、内包for
句の iterablefor
は内包表記のスコープで評価されます。
# NameError
y = [i for i in range(1) for j in range(x)]
# ^^^^^^^^^^^^^^^^^ -----------------
# outer loop inner, nested loop
この設計上の決定は、ジェネレーター式の最も外側の iterable を作成するとエラーがスローされる場合、または最も外側の iterable が iterable ではないことが判明した場合に、反復時間ではなく genexp の作成時にエラーをスローするために行われました。内包表記は、一貫性のためにこの動作を共有します。
フードの下を見る。または、あなたが望んでいたよりもはるかに詳細
dis
モジュールを使用して、これをすべて実際に見ることができます。以下の例では Python 3.3 を使用しています。これは、検査するコード オブジェクトを明確に識別する修飾名を追加するためです。生成されるバイトコードは、それ以外の点では Python 3.2 と機能的に同じです。
クラスを作成するために、Python は基本的に、クラス本体を構成するスイート全体を取得し (したがって、すべてがclass <name>:
行より 1 レベル深くインデントされます)、それを関数であるかのように実行します。
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
最初LOAD_CONST
に、クラス本体のコード オブジェクトをロードしFoo
、それを関数にして呼び出します。その呼び出しの結果は、クラスの名前空間を作成するために使用されます__dict__
。ここまでは順調ですね。
ここで注意すべきことは、バイトコードにネストされたコード オブジェクトが含まれていることです。Python では、クラス定義、関数、内包表記、およびジェネレーターはすべて、バイトコードだけでなく、ローカル変数、定数、グローバルから取得された変数、およびネストされたスコープから取得された変数を表す構造も含むコード オブジェクトとして表されます。コンパイルされたバイトコードはこれらの構造を参照し、Python インタープリターは提示されたバイトコードが与えられた構造にアクセスする方法を知っています。
ここで覚えておくべき重要なことは、Python はコンパイル時にこれらの構造を作成するということです。class
スイートは、既にコンパイルされたコード オブジェクト ( )<code object Foo at 0x10a436030, file "<stdin>", line 2>
です。
クラス本体自体を作成するコード オブジェクトを調べてみましょう。コード オブジェクトのco_consts
構造は次のとおりです。
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
上記のバイトコードは、クラス本体を作成します。関数が実行されlocals()
、 を含む結果の名前空間がクラスの作成x
にy
使用されます (ただしx
、グローバルとして定義されていないため機能しません)。5
に格納した後x
、別のコード オブジェクトをロードすることに注意してください。それがリスト内包表記です。クラス本体と同じように、関数オブジェクトにラップされます。range(1)
作成された関数は、ループ コードに使用する iterableである位置引数を取り、イテレータにキャストします。バイトコードに示されているようにrange(1)
、クラス スコープで評価されます。
このことから、関数またはジェネレーターのコード オブジェクトと内包表記のコード オブジェクトの唯一の違いは、親コード オブジェクトが実行されるとすぐに後者が実行されることです。バイトコードは単純にその場で関数を作成し、いくつかの小さなステップで実行します。
Python 2.x は代わりにインライン バイトコードを使用します。Python 2.7 からの出力は次のとおりです。
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
コード オブジェクトは読み込まれず、代わりにFOR_ITER
ループがインラインで実行されます。そのため、Python 3.x では、リスト ジェネレーターに独自の適切なコード オブジェクトが与えられました。つまり、独自のスコープを持っています。
ただし、モジュールまたはスクリプトがインタープリターによって最初に読み込まれたときに、内包表記は Python ソース コードの残りの部分と共にコンパイルされており、コンパイラーはクラス スイートを有効なスコープとは見なしません。リスト内包表記で参照される変数は、クラス定義を囲むスコープを再帰的に検索する必要があります。変数がコンパイラによって見つからなかった場合、変数はグローバルとしてマークされます。リスト内包コード オブジェクトの逆アセンブルは、x
実際にグローバルとしてロードされていることを示しています。
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
このバイトコードのチャンクは、渡された最初の引数 (range(1)
反復子) をロードし、Python 2.x バージョンがFOR_ITER
それをループして出力を作成するために使用するのと同じようにします。
代わりに関数で定義x
した場合、セル変数になります (セルはネストされたスコープを参照します)。foo
x
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
は、コード オブジェクト セル オブジェクトからLOAD_DEREF
間接的にロードします。x
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
.__closure__
実際の参照では、関数オブジェクトの属性から初期化された現在のフレーム データ構造から値を検索します。内包コード オブジェクト用に作成された関数は再び破棄されるため、その関数のクロージャーを検査することはできません。クロージャーの動作を確認するには、代わりにネストされた関数を検査する必要があります。
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
要約すると、次のようになります。
- Python 3 では、リスト内包表記は独自のコード オブジェクトを取得します。関数、ジェネレーター、または内包表記のコード オブジェクト間に違いはありません。内包コード オブジェクトは一時的な関数オブジェクトにラップされ、すぐに呼び出されます。
- コード オブジェクトはコンパイル時に作成され、非ローカル変数はコードのネストされたスコープに基づいてグローバル変数またはフリー変数としてマークされます。クラス本体は、これらの変数を検索するためのスコープとは見なされません。
- コードを実行するとき、Python はグローバル、または現在実行中のオブジェクトのクロージャーを調べるだけで済みます。コンパイラはクラス本体をスコープとして含めていないため、一時関数の名前空間は考慮されません。
回避策; または、それについて何をすべきか
x
関数のように、変数の明示的なスコープを作成する場合は、リスト内包表記にクラススコープ変数を使用できます。
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
「一時」y
関数は直接呼び出すことができます。戻り値で行う場合は置き換えます。そのスコープは、解決時に考慮されx
ます:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
もちろん、あなたのコードを読んでいる人は、これについて少し頭を悩ませるでしょう。なぜこれを行っているのかを説明する大きな太ったコメントをそこに入れたいと思うかもしれません。
最善の回避策は、__init__
代わりにインスタンス変数を作成するために使用することです。
def __init__(self):
self.y = [self.x for i in range(1)]
頭を悩ませたり、自分自身を説明するための質問をすべて避けてください。namedtuple
あなた自身の具体的な例については、クラスに保存することさえしません。出力を直接使用する (生成されたクラスをまったく保存しない) か、グローバルを使用します。
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]