#38. 内包表記

内包表記は、別の反復可能オブジェクトからコンテナーまたはジェネレーターのような反復子を構築するためのコンパクトな構文です。 CPython は、それらを独自の実行スコープを持つコンパイル済みコード オブジェクトとして実装します。

一般的な形式:python [x * 2 for x in xs] {x * 2 for x in xs} {x: x * 2 for x in xs} (x * 2 for x in xs) これらは以下に対応します。```text list comprehension set comprehension dict comprehension generator expression


## 38.1 リストの内包表記

リスト内包では、熱心にリストを構築します。```python
ys = [x * 2 for x in xs]
```概念的には:```python
ys = []
for x in xs:
    ys.append(x * 2)
```結果は、生成されたすべての値を含むリスト オブジェクトです。

ソース フォームは短くなりますが、実行時の作業は依然として反復、式の評価、および追加操作です。

## 38.2 フィルタリング

理解には以下を含めることができます`if`句。```python
ys = [x * 2 for x in xs if x > 0]
```概念的には:```python
ys = []
for x in xs:
    if x > 0:
        ys.append(x * 2)
```フィルターは項目ごとに実行されます。条件が false の場合、その項目に対して要素式は実行されません。

## 38.3 複数`for`条項

内包表記にはネストされたループを含めることができます。```python
pairs = [(x, y) for x in xs for y in ys]
```概念的には:```python
pairs = []
for x in xs:
    for y in ys:
        pairs.append((x, y))
```順序は左から右で、ネストされたループの順序と一致します。

のために:```python
[(x, y, z) for x in xs for y in ys for z in zs]
```概念的なループのネストは次のとおりです。```python
result = []
for x in xs:
    for y in ys:
        for z in zs:
            result.append((x, y, z))
```## 38.4 複数のフィルター

フィルターは、フィルターが表示されるループ レベルにアタッチされます。```python
result = [x for x in xs if x > 0 if x % 2 == 0]
```概念的には:```python
result = []
for x in xs:
    if x > 0:
        if x % 2 == 0:
            result.append(x)
```複数のループがある場合:```python
result = [(x, y) for x in xs if x > 0 for y in ys if y > x]
```概念的には:```python
result = []
for x in xs:
    if x > 0:
        for y in ys:
            if y > x:
                result.append((x, y))
```後の句は前の句によってバインドされた名前を使用できるため、順序は重要です。

## 38.5 理解範囲

Python 3 では、内包表記には独自のスコープがあります。```python
x = 100
ys = [x for x in range(3)]
print(x)
```出力:```text
100
````x`内包表記の内側は外側を上書きしません`x`

概念的には、内包表記は小さな入れ子関数のように動作します。```python
def _listcomp(iterable):
    result = []
    for x in iterable:
        result.append(x)
    return result

ys = _listcomp(range(3))
```これは正確なソース変換ではありませんが、範囲を説明しています。

## 38.6 内包表記が独自のコードオブジェクトを持つ理由

内包表記には、ループ変数を周囲のスコープに漏らすことなく格納する場所が必要です。

CPython は、多くの内包表記をネストされたコード オブジェクトにコンパイルすることでこの問題を解決します。

例:```python
def f(xs):
    return [x * 2 for x in xs]
```外側の関数には 1 つのコード オブジェクトがあります。リスト内包表記には、外側のコード オブジェクトの定数に格納された別のコード オブジェクトがあります。

これを検査できます:```python
def f(xs):
    return [x * 2 for x in xs]

for const in f.__code__.co_consts:
    print(type(const), const)
```通常、1 つの定数は、理解を容易にするために入れ子になったコード オブジェクトです。

## 38.7 リスト内包表記の逆アセンブル

使用する`dis`:

```python
import dis

def f(xs):
    return [x * 2 for x in xs]

dis.dis(f)
```次に、ネストされたコード オブジェクトを検査します。```python
for const in f.__code__.co_consts:
    if hasattr(const, "co_code"):
        dis.dis(const)
```2 つのレイヤーが表示されます。```text
outer function:
    create comprehension function
    get iterator from xs
    call comprehension function
    return list

inner comprehension:
    build list
    iterate input
    compute x * 2
    append to list
    return list
```正確なバイトコードは CPython のバージョンによって異なりますが、形状は変わりません。

## 38.8 リスト追加の最適化

リスト内包表記では通常、特殊なリスト追加バイトコード パスが使用されます。

概念的には:```python
result.append(value)
```ただし、CPython では、追加のたびに通常のメソッド検索を回避できます。

項目ごとにこれを行う代わりに、次のようにします。```text
load result.append
call append
```内包理解体は内部の追加操作を使用できます。

概念的には:```text
LIST_APPEND
```これにより、繰り返しの属性検索とメソッド呼び出しのオーバーヘッドが節約されます。

これが、リスト内包表記が同等の Python レベルのループよりも高速であることが多い理由の 1 つです。`append`

## 38.9 集合内包表記

集合内包理解は熱心に集合を構築します。```python
unique = {x.lower() for x in words}
```概念的には:```python
unique = set()
for x in words:
    unique.add(x.lower())
```結果には、通常のセットのハッシュ化と等価性に従って一意の要素が含まれます。

セット内包表記は、リスト内包表記が追加動作を使用するのと同様に、内部で set-add 動作を使用します。

## 38.10 辞書の理解

dict 理解は熱心に辞書を構築します。```python
index = {item.id: item for item in items}
```概念的には:```python
index = {}
for item in items:
    index[item.id] = item
```重複したキーが表示された場合、後の値が前の値を上書きします。```python
d = {x % 2: x for x in range(5)}
print(d)
```出力:```text
{0: 4, 1: 3}
```理解は通常の辞書割り当てセマンティクスに従います。

## 38.11 ジェネレータ式

ジェネレーター式は遅延的です。```python
g = (x * 2 for x in xs)
```リストはすぐには作成されません。反復時に値を計算するジェネレーターのようなオブジェクトを作成します。```python
g = (x * 2 for x in range(3))

print(next(g))
print(next(g))
print(next(g))
```出力:```text
0
2
4
```疲れた後は上がる`StopIteration`

## 38.12 ジェネレーター式とリスト内包表記

比較してください:```python
[x * 2 for x in xs]
```そして:```python
(x * 2 for x in xs)
```|特集 |リスト内包表記 |ジェネレータ式 |
|---|---|---|
|評価 |熱心に |怠け者 |
|結果 |リスト |ジェネレーターオブジェクト |
|メモリ |すべての結果を保存します |実行状態を保存します |
|反復 |結果を何度も繰り返すことができます。ワンショット |
|構文 |角括弧 |括弧 |
|使用例 |今すぐすべての値が必要です |ストリーム値 |

完全なリストが必要な場合は、リストの理解の方が速いことがよくあります。

値をストリーミングしたり、早期に停止したりする場合は、ジェネレーター式の方が適していることがよくあります。

## 38.13 ジェネレーター式のワンショットの性質

ジェネレーター式は 1 回消費されます。```python
g = (x for x in range(3))

print(list(g))
print(list(g))
```出力:```text
[0, 1, 2]
[]
```最初`list(g)`それを使い果たす。

再利用可能なデータが必要な場合は、リストまたは別のコンテナを作成します。

## 38.14 早期停止

ジェネレーター式は、早期に停止するコンシューマーに役立ちます。```python
first = next(x for x in xs if x > 100)
```これは、最初に一致する要素が見つかるまでのみ計算されます。

リスト内包バージョン:```python
first = [x for x in xs if x > 100][0]
```最初の項目を選択する前に、一致する完全なリストを作成します。

大きな入力または無限の入力の場合、ジェネレーター式が正しいモデルになります。

## 38.15 内包表記とクロージャ

内包表記は外部変数をキャプチャできます。```python
def scale(xs, factor):
    return [x * factor for x in xs]
```理解は使用します`factor`外部関数から。

概念的には:```text
outer function frame:
    factor stored in local or cell

comprehension code object:
    reads factor as free variable
```コンパイラは、内包表記で外部スコープの変数にアクセスする必要がある場合に、クロージャ セルを配置します。

## 38.16 ループ変数のバインディング

ループ変数は内包スコープに属します。```python
def f():
    x = "outer"
    ys = [x for x in range(3)]
    return x, ys

print(f())
```出力:```text
('outer', [0, 1, 2])
```理解の内側では、`x`内包理解コード オブジェクトのローカルです。

外側`x`は変わらないままです。

## 38.17 内包表記の代入式

代入式は内包表記に使用できます。```python
result = [y for x in xs if (y := f(x)) > 0]
```拘束ルールは微妙です。代入式は、ループ変数と同じ方法で暗黙の内包スコープではなく、包含スコープにバインドされます。

例:```python
def f(xs):
    result = [y for x in xs if (y := x * 2) > 3]
    return y, result
```理解した上で、`y`少なくとも 1 つの代入が発生した場合、それを含む関数スコープで表示される可能性があります。

この動作が存在するのは、割り当て式が、割り当てられた名前を一部の式ローカル コンテキストの外部で使用できるように設計されているためです。

## 38.18 入れ子になった内包表記

内包表記には別の内包表記を含めることができます。```python
matrix = [[i * j for j in range(3)] for i in range(3)]
```概念的には:```python
matrix = []
for i in range(3):
    row = []
    for j in range(3):
        row.append(i * j)
    matrix.append(row)
```各内包表記には独自のコード オブジェクトとスコープがあります。

したがって、ネストされた内包表記では、ネストされた関数のような実行層を作成できます。

## 38.19 辞書よりも内包表記

辞書を反復処理するとキーが生成されます。```python
keys = [k for k in d]
```値を使用するには:```python
values = [v for v in d.values()]
```キーと値のペアを使用するには:```python
pairs = [(k, v) for k, v in d.items()]
```一般的な変換:```python
inverted = {v: k for k, v in d.items()}
```値が重複した場合、辞書キーは一意である必要があるため、後のキーによって前のキーが上書きされます。

## 38.20 内包表記と評価順序

内包句は左から右に実行されます。```python
[(x, y) for x in xs for y in f(x)]
```それぞれについて`x``f(x)`内部反復可能オブジェクトを生成するために評価されます。

概念的には:```python
result = []
for x in xs:
    for y in f(x):
        result.append((x, y))
```これは、後の句が前のループ変数に依存できることを意味します。

要素式は、その出力値に対するすべてのループ句とフィルター句が成功した後にのみ実行されます。

## 38.21 副作用

内包表記には副作用が含まれる場合がありますが、通常は値を生成するために使用する必要があります。

可能性はあるがスタイルが悪い:```python
[print(x) for x in xs]
```これにより、次のリストが作成されます`None`印刷を実行するためだけに値を設定します。

好む:```python
for x in xs:
    print(x)
```結果が重要な場合は内包を使用します。

## 38.22 内包表記の例外

例外は通常どおり伝播します。```python
result = [10 / x for x in xs]
```もし`x`ゼロです、`ZeroDivisionError`伝播して理解が止まってしまいます。

部分的に構築された内部コンテナは、他の場所で参照されない限り破棄されます (通常の内部理解では公開されません)

ジェネレーター式の場合、例外は遅延して発生します。```python
g = (10 / x for x in xs)
```作成`g`分けません。例外は、問題のあるアイテムがリクエストされたときに発生します。

## 38.23 内包表記と`try`内包表記では次のようなステートメントは許可されません。`try`直接彼らの中にあります。

無効:```python
[x for x in xs try ...]
```ヘルパー関数を使用します。```python
def parse_or_none(x):
    try:
        return int(x)
    except ValueError:
        return None

values = [y for x in xs if (y := parse_or_none(x)) is not None]
```または、例外処理が中心の場合は通常のループを使用します。```python
values = []
for x in xs:
    try:
        values.append(int(x))
    except ValueError:
        pass
```## 38.24 非同期内包表記

内部`async def`、内包表記は使用できます`async for````python
async def collect(stream):
    return [item async for item in stream]
```概念的には:```python
result = []
async for item in stream:
    result.append(item)
return result
```使用することもできます`await`要素式またはフィルター内:```python
async def collect(xs):
    return [await process(x) for x in xs]
```非同期内包表記は非同期対応バイトコードにコンパイルされ、実行中に一時停止する可能性があります。

## 38.25 非同期ジェネレーター式

非同期ジェネレーター式で使用できるのは、`async for````python
gen = (item async for item in stream)
```これは、次のように消費される非同期ジェネレーターのようなオブジェクトを生成します。`async for`または`anext````python
async for item in gen:
    ...
```実行モデルは、理解スコープと非同期反復およびコルーチンの一時停止を組み合わせています。

## 38.26 内包表記と`locals()`内包には独自の範囲があるため、`locals()`内包表記のようなヘルパー内では、周囲のローカル変数ではなく、内包表記のローカル変数が表示されます。

これは、内包表記によってステートメントが制限されるため、直接構文よりもヘルパー関数を使用した方が簡単に確認できます。

重要なルール:```text
loop variables in comprehensions do not leak into the surrounding scope
```通常のコードの場合は、詳細ではなくそのルールに依存します。`locals()`実装によって作成されたフレーム内。

## 38.27 内包表記と遅延バインディング

内包表記内のクロージャは依然として遅延バインディング動作を示す可能性があります。```python
funcs = [lambda: x for x in range(3)]
print([f() for f in funcs])
```出力:```text
[2, 2, 2]
```各ラムダは同じ内包変数に対して閉じます`x`、最終的な値は`2`

デフォルトの引数を使用して現在の値を取得します。```python
funcs = [lambda x=x: x for x in range(3)]
print([f() for f in funcs])
```出力:```text
[0, 1, 2]
```内包スコープは外部へのリークを防ぎますが、クロージャの反復ごとに新しいバインディングを作成しません。

## 38.28 内包表記と参照の有効期間

リスト内包表記は、何かがフレームをキャプチャしない限り、完了後にそのフレームを生きたままにしません。

ただし、ジェネレーター式は、一時停止中もフレームのような状態を維持します。```python
g = (x * 2 for x in range(10))
```ジェネレータ式には次のものが保持されます。```text
code object
iteration state
current iterator
locals
suspended frame state
```大きなオブジェクトを捕捉した場合、ジェネレーターが使い果たされるか破棄されるまで、そのオブジェクトは生き続ける可能性があります。```python
def f():
    big = bytearray(100_000_000)
    return (x for x in range(3) if big is not None)

g = f()
```ここ、`big`ジェネレーター式のクロージャーを通じて存続します。

## 38.29 理解とパフォーマンス

CPython は特殊な内部操作を使用できるため、リスト内包表記は同等のループよりも高速であることがよくあります。

例:```python
result = []
for x in xs:
    result.append(x * 2)
```比較:```python
result = [x * 2 for x in xs]
```この理解により、Python レベルのメソッド検索を繰り返す必要がなくなります。`append`

ただし、パフォーマンスは式、データ サイズ、Python のバージョン、遅延が重要かどうかによって異なります。

原則:```text
use list/set/dict comprehensions when building that container directly
use generator expressions when streaming or stopping early
use explicit loops when control flow is complex
```## 38.30 理解力と可読性

理解は、1 つの単純な変換に適合する場合に最も明確になります。

良い:```python
names = [user.name for user in users]
```良い:```python
active = [user for user in users if user.active]
```多くの場合、密度が高すぎる場合:```python
result = [(a, b, c) for a in xs if p(a) for b in f(a) if q(b) for c in g(a, b) if r(c)]
```多くの句、副作用、例外処理、または複雑な分岐がある場合は、明示的なループを使用します。

## 38.31 CPython オブジェクト フロー

リストを理解するには:```python
[x * 2 for x in xs]
```CPython は概念的に次のことを実行します。```text
create result list
get iterator from xs
loop:
    get next item
    store item in comprehension local x
    load x
    load constant 2
    multiply
    append to result list
return result list
```リスト オブジェクトは、構築中は内包フレーム内に保持されます。

dict 理解の場合:```python
{x: x * 2 for x in xs}
```流れは次のとおりです。```text
create result dict
iterate xs
compute key
compute value
store key-value pair
return dict
```## 38.32 内包コードのオブジェクト名

内包コード オブジェクトには次のような内部名があります。```text
<listcomp>
<setcomp>
<dictcomp>
<genexpr>
```それらはトレースバックとイントロスペクションで確認できます。

例:```python
def f(xs):
    return [10 / x for x in xs]

f([2, 1, 0])
```トレースバックには以下が含まれる場合があります`<listcomp>`例外は内包コードオブジェクト内で発生するためです。

これは、理解の実行には独自のフレームのようなコンテキストがあることを示しています。

## 38.33 内包表記のトレースバック

内包表記内で例外が発生した場合、トレースバックには外部関数と内包表記の両方が含まれることがあります。```python
def f(xs):
    return [10 / x for x in xs]

f([1, 0])
```ゼロ除算は内包コード内で行われます。

概念的には:```text
frame f
    calls <listcomp>
        division by zero
```これは、内包コード オブジェクトのもう 1 つの目に見える効果です。

## 38.34 内包変数の有効期間

内包ループ変数が内包スコープ内に存在します。

完了後:```python
def f():
    result = [x for x in range(3)]
    return "x" in locals()

print(f())
```これは以下を返します:```text
False
```ループ変数`x`の地元民にはならなかった`f`

理解の枠組みの中で、`x`理解が進んでいる間は存在していました。

## 38.35 ジェネレータ式の引数のショートカット

ジェネレーター式は、追加のかっこなしで、関数への唯一の引数として渡すことができます。```python
total = sum(x * x for x in xs)
```これは次と同等です。```python
total = sum((x * x for x in xs))
```ただし、引数が複数ある場合は括弧が必要です。```python
result = func((x for x in xs), other)
```これは構文レベルの利便性です。ランタイム オブジェクトは依然としてジェネレーター式です。

## 38.36 内包表記と組み込み

内包表記は多くの場合、組み込みと組み合わせられます。

:```python
sum(x for x in xs)
any(x > 0 for x in xs)
all(x.valid for x in items)
max(score(x) for x in xs)
```これらはジェネレータ式を使用するため、場合によっては早期に停止する可能性があります。`any`最初の真の値で停止します。`all`最初の false 値で停止します。`sum`ジェネレーター全体を消費します。

ジェネレータ式を選択すると、不必要な中間リストの構築を回避できます。

## 38.37 よくある誤解

|誤解 |正しいモデル |
|---|---|
|内包表記は、同じスコープ内で構文を書き直すだけです。通常、独自のネストされたコード オブジェクトとスコープがあります。
|ループ変数が外側のスコープに漏れます。 Python 3 では、そうではありません。
|ジェネレータ式はタプルを構築します |ジェネレーター オブジェクトを作成します。
|リストの内包理解は常に優れています。ジェネレーター式はストリーミングと早期停止に適しています。
|内包表記は外部変数をキャプチャできません。クロージャを介してキャプチャできます。
|内包表記内の各ラムダは、異なるループ変数をキャプチャします。これらは通常、同じ内包変数バインディングを共有します。
|ジェネレーター式の作成時に例外が発生します。それらは消費されると起こります。
|辞書内包表記は重複キーを保持します。後の値は前の値を上書きします。

## 38.38 読書戦略

以下から始めます:```python
def f(xs):
    return [x * 2 for x in xs if x > 0]
```検査:```python
import dis

dis.dis(f)

for const in f.__code__.co_consts:
    if hasattr(const, "co_code"):
        print(const.co_name)
        dis.dis(const)
```次に、以下と比較します。```python
def g(xs):
    return (x * 2 for x in xs if x > 0)
```追跡:```text
outer function bytecode
nested comprehension code object
iteration setup
local loop variable
filter jump
append or yield operation
return value
closure variables
```次に、setdictnestedasync の内包表記を学習します。

## 38.39 章の概要

内包表記は、リスト、セット、辞書、またはジェネレーターのような反復子を構築するためのコンパイルされた実行ユニットです。これらは、反復、フィルタリング、式評価、バインディング、およびコンテナ構築を式形式で組み合わせます。

コアモデルは次のとおりです。```text
evaluate outer iterable
    
create comprehension execution scope
    
iterate
    
apply filters
    
compute element, key-value pair, or yielded value
    
append, add, store, or yield
    
return container or generator object
```listsetdict の内包表記は熱心です。ジェネレーター式は怠惰です。 CPython は、ネストされたコード オブジェクト、フレーム状態、クロージャ処理、特殊なバイトコード操作、および通常の例外伝播を使用してこれらの構造を実装します。