ジェネレーター式は、ある種の関数を作成します。引数が 1 つだけで、最も外側の iterable です。
unitsこれは であり、ジェネレーター式が作成されるときにジェネレーター式への引数としてバインドされます。
他のすべての名前は、ローカル (aおよび などb)、グローバル、またはクロージャーのいずれかです。tensはグローバルとして検索されるため、ジェネレーターを進めるたびに検索されます。
その結果、units3 行目のジェネレーターにバインドさtensれ、最終行のジェネレーター式を反復処理したときにルックアップされます。
これは、ジェネレーターをバイトコードにコンパイルし、そのバイトコードを検査するときに確認できます。
>>> import dis
>>> genexp_bytecode = compile('(a + b for a in units for b in tens)', '<file>', 'single')
>>> dis.dis(genexp_bytecode)
1 0 LOAD_CONST 0 (<code object <genexpr> at 0x10f013ae0, file "<file>", line 1>)
3 LOAD_CONST 1 ('<genexpr>')
6 MAKE_FUNCTION 0
9 LOAD_NAME 0 (units)
12 GET_ITER
13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
16 PRINT_EXPR
17 LOAD_CONST 2 (None)
20 RETURN_VALUE
バイトコードはMAKE_FUNCTIONジェネレータ式コード オブジェクトを関数に変換し、引数として渡してすぐに呼び出されiter(units)ます。ここtensでは名前はまったく言及されていません。
これは、元のジェネレーター PEPに記載されています。
最も外側の for 式のみがすぐに評価され、他の式はジェネレーターが実行されるまで延期されます。
g = (tgtexp for var1 in exp1 if exp2 for var2 in exp3 if exp4)
次と同等です。
def __gen(bound_exp):
for var1 in bound_exp:
if exp2:
for var2 in exp3:
if exp4:
yield tgtexp
g = __gen(iter(exp1))
del __gen
およびジェネレーター式の参照では:
ジェネレーター式で使用される変数は、__next__()ジェネレーター オブジェクトに対してメソッドが呼び出されると、(通常のジェネレーターと同じ方法で) 遅延評価されます。ただし、一番左のfor句はすぐに評価されるため、ジェネレータ式を処理するコード内で他の可能性のあるエラーが発生する前に、それによって生成されたエラーを確認できます。後続forの句は、前の for ループに依存する可能性があるため、すぐに評価できません。例: (x*y for x in range(10) for y in bar(x)).
PEP には、 (最も外側の iterable 以外の) 名前が遅くバインドされる理由を説明する優れたセクションがあります。 Early Binding vs. Late Bindingを参照してください。