クラスのスコープとリスト、セットまたはディクショナリの理解、およびジェネレーターの式は混在しません。
その理由; または、これに関する公式の言葉
Python 3では、リスト内包表記に独自の適切なスコープ(ローカル名前空間)が与えられ、ローカル変数が周囲のスコープに流れ込むのを防ぎました(Pythonリスト内包表記は、内包範囲の後でさえ名前を再バインドします。これは正しいですか?)。モジュールや関数でこのようなリスト内包表記を使用する場合は素晴らしいことですが、クラスでは、スコープが少し変わっています。
これは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のバージョンに関係なく独自のスコープを持っているため、セットまたはディクトリ内包表記を使用した同様の例は、Python 2では機能しません。
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
(小さな)例外。または、なぜ一部がまだ機能するのか
Pythonのバージョンに関係なく、周囲のスコープで実行される内包表現またはジェネレータ式の一部があります。それが最も外側のイテラブルの式になります。あなたの例では、それはrange(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
したがって、x
その式で使用してもエラーはスローされません。
# Runs fine
y = [i for i in range(x)]
これは最も外側のイテラブルにのみ適用されます。内包に複数のfor
節がある場合、内for
節の反復可能オブジェクトは内包のスコープで評価されます。
# NameError
y = [i for i in range(1) for j in range(x)]
この設計上の決定は、ジェネレーター式の最も外側の反復可能オブジェクトを作成するとき、または最も外側の反復可能オブジェクトが反復可能でないことが判明したときに、反復時間ではなく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
クラス本体のコードオブジェクトを読み込み、それを関数にして呼び出します。次に、その呼び出しの結果を使用して、クラスの名前空間itsが作成されます__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)
ループコードに使用する反復可能)を取り、反復子にキャストします。バイトコードに示されているように、range(1)
クラススコープで評価されます。
これから、関数またはジェネレータのコードオブジェクトと内包のコードオブジェクトの唯一の違いは、後者が親コードオブジェクトが実行されるとすぐに実行されることです。バイトコードは、その場で関数を作成し、いくつかの小さなステップで実行します。
Python 2.xは代わりにインラインバイトコードを使用します。Python2.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'),
# ...
]]
NameError: global name 'x' is not defined
はPython 3.2と3.3を使用していますが、これは私が期待していることです。