34. 内包表記: リスト・辞書・セットをコンパクトに作成する方法
内包表記(comprehension)は Python の最も洗練された機能の1つで、コレクション(collection)を1行の読みやすいコードで作成したり変換したりできます。ループ(loop)と append 操作で複数行書く代わりに、内包表記を使えば同じロジックをより簡潔に、そして多くの場合より明確に表現できます。
この章では、リスト内包表記(list comprehension)、辞書内包表記(dictionary comprehension)、セット内包表記(set comprehension)を使って、より Pythonic なコードを書く方法を学びます。条件分岐をどう組み込むか、従来のループより内包表記を選ぶべき場面はいつか、さらにネストした反復でより複雑なケースをどう扱うかも見ていきます。
34.1) リストを作成・変換するためのリスト内包表記
34.1.1) 基本的なリスト内包表記の構文
リスト内包表記(list comprehension)は、既存のシーケンス(sequence)の各要素に式(expression)を適用して、新しいリスト(list)を作るためのコンパクトな方法です。基本構文は次のとおりです。
[expression for item in iterable]これは、iterable(リストや range、文字列など、ループで回せる任意のシーケンス)の各 item について expression を評価した結果を要素として持つ、新しいリストを作成します。
まずは簡単な例から始めましょう。0 から 4 までの数の平方のリストを作りたいとします。
# ループを使った従来の方法
squares = []
for number in range(5):
squares.append(number ** 2)
print(squares) # Output: [0, 1, 4, 9, 16]リスト内包表記を使うと、より簡潔に書けます。
# リスト内包表記を使用
squares = [number ** 2 for number in range(5)]
print(squares) # Output: [0, 1, 4, 9, 16]どちらの方法でも結果は同じですが、内包表記のほうがコンパクトで、構文に慣れると多くの場合読みやすくなります。内包表記では、平方した値のリストを作っていることがはっきり分かります。
34.1.2) 既存データの変換
リスト内包表記は、データをある形式から別の形式へ変換するのが得意です。実用的な例をいくつか見てみましょう。
温度を摂氏から華氏に変換する:
# 摂氏の温度データ
celsius_temps = [0, 10, 20, 30, 40]
# 次の式で華氏に変換: F = C * 9/5 + 32
fahrenheit_temps = [temp * 9/5 + 32 for temp in celsius_temps]
print(fahrenheit_temps) # Output: [32.0, 50.0, 68.0, 86.0, 104.0]文字列を大文字に変換する:
# 大文字小文字が混在した商品コード
product_codes = ["abc123", "def456", "ghi789"]
# 大文字に統一
uppercase_codes = [code.upper() for code in product_codes]
print(uppercase_codes) # Output: ['ABC123', 'DEF456', 'GHI789']34.1.3) range オブジェクトからリストを作成する
リスト内包表記は、12章で学んだ range() と自然に組み合わせられます。これは、特定のパターンを持つシーケンスを生成するときに便利です。
# 0 から 10 までの偶数を生成
evens = [n * 2 for n in range(6)] # n は 0 から 5 までなので、n*2 は 0, 2, 4, 6, 8, 10 になります
print(evens) # Output: [0, 2, 4, 6, 8, 10]
# 5 の倍数を生成
multiples_of_five = [n * 5 for n in range(1, 6)]
print(multiples_of_five) # Output: [5, 10, 15, 20, 25]34.1.4) 内包表記 vs append でリストを構築する方法
リスト内包表記は1回の操作でリスト全体を作成し、従来のループはリストを段階的に構築する、という違いを理解することが重要です。どちらも同じ結果になりますが、内包表記は一般に新しいリストを作るのが速く、より Pythonic とされています。
次は並べた比較です。
# 従来のループによる方法
result = []
for i in range(5):
result.append(i * 3)
print(result) # Output: [0, 3, 6, 9, 12]
# リスト内包表記による方法
result = [i * 3 for i in range(5)]
print(result) # Output: [0, 3, 6, 9, 12]どちらも有効ですが、内包表記のほうが簡潔で、意図(「各値が i * 3 のリストを作る」)が明確に表れます。
34.2) リスト内包表記の中の条件ロジック
34.2.1) if 条件でフィルタリングする
リスト内包表記の強力な機能の1つは、条件に基づいて要素をフィルタリングできることです。内包表記の末尾に if 句を追加して、条件を満たす項目だけを含められます。
[expression for item in iterable if condition]if 句は フィルタ(filter)として働きます。Python は各要素ごとに条件を評価し、条件が True の要素だけが結果のリストに含まれます。条件を満たさない要素は完全にスキップされます。
簡単な例で動きを確認しましょう。
# 0 から 9 までのうち偶数だけを取得
numbers = range(10)
evens = [n for n in numbers if n % 2 == 0]
print(evens) # Output: [0, 2, 4, 6, 8]ここでは、n % 2 == 0 が数が偶数かどうかをチェックしています。このテストを通過した数だけが新しいリストに含まれます。
学生の点数のフィルタリング:
# 学生のテスト点数
scores = [45, 78, 92, 65, 88, 55, 73, 95]
# 合格点(>= 70)だけを取得
passing_scores = [score for score in scores if score >= 70]
print(passing_scores) # Output: [78, 92, 88, 73, 95]34.2.2) フィルタした項目を変換する
フィルタリングと変換を組み合わせて、フィルタされた要素に式を適用できます。
# 学生の点数
scores = [45, 78, 92, 65, 88, 55, 73, 95]
# 合格点を取得し、0-10 の範囲にスケールする
scaled_passing = [score / 10 for score in scores if score >= 70]
print(scaled_passing) # Output: [7.8, 9.2, 8.8, 7.3, 9.5]
# まずフィルタ(>= 70 だけを残す)してから、変換(10 で割る)します文字列の変換とフィルタリング:
# 品質が混在した商品名
products = ["apple", "BANANA", "cherry", "DATE", "elderberry"]
# 名前の長さが 5 文字より長い商品の大文字版を取得
long_products_upper = [product.upper() for product in products if len(product) > 5]
print(long_products_upper) # Output: ['BANANA', 'CHERRY', 'ELDERBERRY']34.2.3) 内包表記で条件式(if-else)を使う
場合によっては、条件で要素を除外するのではなく、条件に応じて要素を別の形に変換したいことがあります。その場合は、内包表記の式の部分で 条件式(conditional expression)(10章で学びました)を使います。
[expression_if_true if condition else expression_if_false for item in iterable]これはフィルタリングとは異なります。ここでは すべての要素 が結果に含まれ、if-else が各要素にどの式を適用するかを決めます。条件式(10章)は、for 句の前、つまり式の部分に書かれます。
構文の違いに注意してください:
- フィルタリング:
[expr for item in seq if condition]-ifは末尾、elseはなし - 条件式:
[expr_if if cond else expr_else for item in seq]-if-elseは式の中で、forの前
# 数を偶数/奇数に分類
numbers = range(6)
classifications = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(classifications) # Output: ['even', 'odd', 'even', 'odd', 'even', 'odd']条件に応じて異なる変換を適用する:
# 学生の点数
scores = [45, 78, 92, 65, 88, 55, 73, 95]
# 不合格点にはボーナス点を加え、合格点はそのままにする
adjusted_scores = [score + 10 if score < 70 else score for score in scores]
print(adjusted_scores) # Output: [55, 78, 92, 75, 88, 65, 73, 95]どちらの例でも、次の点に注目してください。
- 元のリストのすべての要素が結果に含まれています
if-elseが各要素がどの値になるかを決めています- 要素は1つも除外されません
34.2.4) 違いを理解する: フィルタリング vs 条件式
この2つのパターンの違いを理解することは非常に重要です。
フィルタリング(末尾に if) - 一部の要素は除外されます:
# 正の数だけを含める
numbers = [-2, 5, -1, 8, 0, 3]
positives = [n for n in numbers if n > 0]
print(positives) # Output: [5, 8, 3]
print(len(positives)) # Output: 3 (3 個だけ)
# 処理: 条件をチェック → True なら要素を含める → False なら要素をスキップ条件式(式の中に if-else) - すべての要素が含まれますが、変換の仕方が異なります:
# 負の数は 0 に変換し、正の数はそのままにする
numbers = [-2, 5, -1, 8, 0, 3]
non_negatives = [n if n > 0 else 0 for n in numbers]
print(non_negatives) # Output: [0, 5, 0, 8, 0, 3]
print(len(non_negatives)) # Output: 6 (6 個すべて)
# 処理: 条件をチェック → True なら最初の式を使う → False なら2番目の式を使う → 常に結果を含める34.3) 辞書内包表記
34.3.1) 基本的な辞書内包表記の構文
リスト内包表記がリストを作るのと同様に、辞書内包表記(dictionary comprehension)は辞書(dictionary)を作ります。構文は似ていますが、キー(key)と値(value)の両方を指定します。
{key_expression: value_expression for item in iterable}これにより、iterable から生成されたキーと値のペアで構成される新しい辞書が作られます。
まずは、数を平方に対応付ける辞書を作る簡単な例です。
# 数とその平方の辞書を作成
squares_dict = {n: n ** 2 for n in range(5)}
print(squares_dict) # Output: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}2つのリストから辞書を作成する:
# 学生名と点数
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
# 名前から点数への辞書を作成
student_scores = {names[i]: scores[i] for i in range(len(names))}
print(student_scores) # Output: {'Alice': 85, 'Bob': 92, 'Charlie': 78}2つのシーケンスをよりエレガントに結合する方法として zip() がありますが、これは37章で学びます。ここでは、インデックスベースの方法でも十分に機能します。
34.3.2) 既存の辞書を変換する
辞書内包表記は、既存の辞書を変換するのにも優れています。キー、値、またはその両方を変更できます。
辞書を内包表記で反復処理する場合は、キーと値の両方にアクセスするために .items() を使います。.items() メソッドはキーと値のペアを返し、for 句でアンパックできます。
# 元の価格(ドル)
prices = {"apple": 1.50, "banana": 0.75, "cherry": 2.00}
# セントに変換(100 倍)
prices_in_cents = {fruit: price * 100 for fruit, price in prices.items()}
print(prices_in_cents) # Output: {'apple': 150.0, 'banana': 75.0, 'cherry': 200.0}キーを変換する:
# 小文字の商品コード
codes = {"abc": 100, "def": 200, "ghi": 300}
# キーを大文字に変換
uppercase_codes = {code.upper(): quantity for code, quantity in codes.items()}
print(uppercase_codes) # Output: {'ABC': 100, 'DEF': 200, 'GHI': 300}キーと値の両方を変換する:
# 学生名と点数
scores = {"alice": 85, "bob": 92, "charlie": 78}
# 名前を先頭大文字にし、点数を 0-10 の範囲にスケールする
formatted_scores = {name.capitalize(): score / 10 for name, score in scores.items()}
print(formatted_scores) # Output: {'Alice': 8.5, 'Bob': 9.2, 'Charlie': 7.8}34.3.3) 辞書項目をフィルタリングする
リスト内包表記と同様に、辞書内包表記でも条件を入れて項目をフィルタできます。
# 学生の点数
scores = {"Alice": 85, "Bob": 65, "Charlie": 92, "David": 55, "Eve": 78}
# 合格点(>= 70)だけを取得
passing_scores = {name: score for name, score in scores.items() if score >= 70}
print(passing_scores) # Output: {'Alice': 85, 'Charlie': 92, 'Eve': 78}キーの特徴でフィルタリングする:
# 商品在庫
inventory = {"apple": 50, "banana": 30, "apricot": 20, "cherry": 40}
# 'a' で始まる商品だけを取得
a_products = {product: quantity for product, quantity in inventory.items()
if product.startswith('a')}
print(a_products) # Output: {'apple': 50, 'apricot': 20}34.3.4) シーケンスから辞書を作成する
辞書内包表記は、シーケンスからルックアップ用の辞書を作るのに便利です。
# 単語のリスト
words = ["python", "java", "ruby", "javascript"]
# 各単語を長さに対応付ける辞書を作成
word_lengths = {word: len(word) for word in words}
print(word_lengths) # Output: {'python': 6, 'java': 4, 'ruby': 4, 'javascript': 10}34.3.5) 辞書内包表記で条件式を使う
条件に応じて値の計算を変えるために、条件式を使えます。
# 学生の点数
scores = {"Alice": 85, "Bob": 65, "Charlie": 92, "David": 55}
# "Pass" / "Fail" のステータスを追加
scores_with_status = {name: "Pass" if score >= 70 else "Fail"
for name, score in scores.items()}
print(scores_with_status) # Output: {'Alice': 'Pass', 'Bob': 'Fail', 'Charlie': 'Pass', 'David': 'Fail'}異なる変換を適用する:
# 商品価格
prices = {"apple": 1.50, "banana": 0.75, "cherry": 2.50}
# 高い商品(> $2.00)に割引を適用
discounted_prices = {product: price * 0.9 if price > 2.00 else price
for product, price in prices.items()}
print(discounted_prices) # Output: {'apple': 1.5, 'banana': 0.75, 'cherry': 2.25}34.4) セット内包表記
34.4.1) 基本的なセット内包表記の構文
セット内包表記(set comprehension)は、リスト内包表記に似た構文でセット(set)を作成しますが、中括弧 {} を使います。
{expression for item in iterable}結果はセットなので、重複した値は自動的に取り除かれ、順序は保証されません。
# 平方のセットを作成
squares_set = {n ** 2 for n in range(6)}
print(squares_set) # Output: {0, 1, 4, 9, 16, 25}リスト内包表記との重要な違いは、セットが自動的に重複を取り除く点です。
# リスト内包表記 - 重複を保持
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
squared_list = [n ** 2 for n in numbers]
print(squared_list) # Output: [1, 4, 4, 9, 9, 9, 16, 16, 16, 16]
# セット内包表記 - 重複を除去
squared_set = {n ** 2 for n in numbers}
print(squared_set) # Output: {16, 1, 4, 9} (order may vary)セットの出力順序は、ここに示したものと異なる場合があります。セットは順序を持たないコレクションなので、Python は要素をどの順序で表示してもよいからです。
34.4.2) ユニークな値を抽出する
セット内包表記は、コレクションからユニークな値を抽出する必要があるときに最適です。
# 学生の回答(重複あり)
responses = ["yes", "no", "yes", "maybe", "no", "yes", "maybe"]
# ユニークな回答を取得
unique_responses = {response for response in responses}
print(unique_responses) # Output: {'maybe', 'yes', 'no'}文字列からユニークな文字を抽出する:
# 繰り返し文字を含むテキスト
text = "mississippi"
# ユニークな文字を取得
unique_chars = {char for char in text}
print(unique_chars) # Output: {'m', 'i', 's', 'p'}34.4.3) セット内包表記で変換・フィルタリングする
他の内包表記と同様に、セット内包表記にも変換や条件を含められます。
# 学生名
names = ["Alice", "bob", "CHARLIE", "david", "EVE"]
# 先頭文字を大文字にしたユニークな集合を取得
first_letters = {name[0].upper() for name in names}
print(first_letters) # Output: {'A', 'B', 'C', 'D', 'E'}条件でフィルタリングする:
# 重複を含む数
numbers = [1, -2, 3, -4, 5, -2, 3, 6, -4]
# 正の数だけをユニークに取得
positive_numbers = {n for n in numbers if n > 0}
print(positive_numbers) # Output: {1, 3, 5, 6}34.4.4) セット内包表記が最も役立つ場面
セット内包表記が特に価値を発揮するのは、次のような場合です。
- ユニークな値が必要: 重複を自動的に取り除きます
- 順序が重要ではない: セットは順序を持たないため、順番が重要でないときに使います
- 集合演算を行う: 結果を和集合、積集合などに使えます(17章で学びました)
# 2つのコースの受講者
course_a = ["Alice", "Bob", "Charlie", "David"]
course_b = ["Charlie", "David", "Eve", "Frank"]
# セット内包表記で両コースのユニークな学生を取得
all_students = {student for course in [course_a, course_b] for student in course}
print(all_students) # Output: {'Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'}34.5) 内包表記とループのどちらを選ぶか
34.5.1) 内包表記が適しているとき
内包表記は、既存のコレクションを変換したりフィルタしたりして新しいコレクションを作る場合に、一般的に好まれます。より簡潔で、多くの場合より読みやすく、同等のループより通常は高速です。
内包表記が得意な場面:
- 既存のコレクションから新しいコレクションを作る:
# 内包表記の良い使い方
prices = [10.99, 25.50, 8.75, 15.00]
discounted = [price * 0.9 for price in prices]- 変換が単純で分かりやすい:
# 明快で簡潔
names = ["alice", "bob", "charlie"]
uppercase_names = [name.upper() for name in names]- 単純な条件でフィルタリングする:
# 理解しやすい
scores = [85, 92, 78, 65, 88, 55, 73, 95]
passing = [score for score in scores if score >= 70]34.5.2) 従来のループが適しているとき
一方で、従来のループのほうが適切で読みやすい状況もあります。
次のような場合はループを使います:
- ロジックが複雑、または複数ステップを含む:
# 内包表記にするには複雑すぎる
results = []
for score in scores:
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
results.append({"score": score, "grade": grade})この例を内包表記として書くことも できます が、読みづらくなります。
- コレクションの作成以外の処理が必要:
# I/O や副作用を伴う場合はループのほうが明確
for filename in files:
with open(filename) as f:
content = f.read()
print(f"Processing {filename}")
# ... more processing- 既存のコレクションをその場で変更する必要がある:
# リストをその場で変更 - 内包表記は使えない
numbers = [1, 2, 3, 4, 5]
for i in range(len(numbers)):
numbers[i] *= 2
print(numbers) # Output: [2, 4, 6, 8, 10]- 複雑なロジックで break や continue を使う必要がある:
# 追加の処理をしつつ最初の一致を探す
found = None
for item in items:
if item.startswith("target"):
found = item
print(f"Found: {found}")
break34.5.3) 可読性の考慮
最も重要な要素は可読性です。内包表記が長くなりすぎたり複雑になったりする場合は、従来のループに分解しましょう。
# 読みにくい - 1行に詰め込みすぎ
result = [item.upper().strip() for item in items if len(item) > 5 and item.startswith('a')]
# より良い - ロジックが複雑なときはループを使う
result = []
for item in items:
if len(item) > 5 and item.startswith('a'):
cleaned = item.strip().upper()
result.append(cleaned)良い目安: 内包表記が気持ちよく1行に収まらない(または明確な整形をしてもせいぜい2行)なら、代わりにループを使うことを検討してください。
34.5.4) パフォーマンスの考慮
内包表記は、インタプリタレベルで最適化されているため、一般に同等のループより高速です。ただし、この性能差は小〜中規模のコレクションでは通常ほとんど気になりません。
# どちらも同じ結果になる
# 内包表記のほうがわずかに速い
squares_comp = [n ** 2 for n in range(1000)]
# ループのほうがわずかに遅いが柔軟性が高い
squares_loop = []
for n in range(1000):
squares_loop.append(n ** 2)実用上は、パフォーマンスよりも可読性で選びましょう。プロファイリングで特定の処理がボトルネックだと分かった場合にのみ、速度最適化を行ってください。
34.5.5) アプローチを組み合わせる
ときには、両方のアプローチを組み合わせるのが最善の解決策になることもあります。
# 単純な変換には内包表記を使う
student_data = [
{"name": "Alice", "score": 85},
{"name": "Bob", "score": 92},
{"name": "Charlie", "score": 78}
]
# 内包表記で点数を抽出
scores = [student["score"] for student in student_data]
# 複雑な処理にはループを使う
for student in student_data:
score = student["score"]
if score >= 90:
print(f"{student['name']}: Excellent!")
elif score >= 80:
print(f"{student['name']}: Good job!")
else:
print(f"{student['name']}: Keep working!")34.6) ネストしたループと複数の for 句
34.6.1) 複数の for 句を理解する
内包表記には複数の for 句を含められ、これはネストしたループと同等です。構文は次のとおりです。
[expression for item1 in iterable1 for item2 in iterable2]これは次と同等です。
result = []
for item1 in iterable1:
for item2 in iterable2:
result.append(expression)重要なポイントは、for 句は 左から右 に読むということです。これは、ネストしたループを 上から下 に書くのと同じです。
2つのリストのすべての組み合わせを作る簡単な例から始めましょう。
# 2つの値のリスト
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
# すべての組み合わせを作成
combinations = [(color, size) for color in colors for size in sizes]
print(combinations)
# Output: [('red', 'S'), ('red', 'M'), ('red', 'L'), ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]これは、色とサイズのあらゆる組み合わせを作成します。
34.6.2) 座標ペアを作成する
よくある用途として、座標ペアを生成するケースがあります。
# 3x3 の座標グリッドを作成
coordinates = [(x, y) for x in range(3) for y in range(3)]
print(coordinates)
# Output: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]掛け算表を作る:
# 掛け算の組を生成
products = [(x, y, x * y) for x in range(1, 4) for y in range(1, 4)]
for x, y, product in products:
print(f"{x} × {y} = {product}")
# Output:
# 1 × 1 = 1
# 1 × 2 = 2
# 1 × 3 = 3
# 2 × 1 = 2
# 2 × 2 = 4
# 2 × 3 = 6
# 3 × 1 = 3
# 3 × 2 = 6
# 3 × 3 = 934.6.3) ネストしたリストをフラット化する
複数の for 句は、ネストした構造をフラット化するのに便利です。
# ネストした数値リスト
nested_numbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 単一のリストにフラット化
flat = [num for sublist in nested_numbers for num in sublist]
print(flat) # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]これは次と同等です。
flat = []
for sublist in nested_numbers:
for num in sublist:
flat.append(num)単語のリストを文字にフラット化する:
# 単語のリスト
words = ["cat", "dog", "bird"]
# すべての単語のすべての文字を取得
all_chars = [char for word in words for char in word]
print(all_chars) # Output: ['c', 'a', 't', 'd', 'o', 'g', 'b', 'i', 'r', 'd']34.6.4) ネストした内包表記に条件を追加する
条件を追加して結果をフィルタできます。
# 合計が偶数になるペアを作成
pairs = [(x, y) for x in range(5) for y in range(5) if (x + y) % 2 == 0]
print(pairs)
# Output: [(0, 0), (0, 2), (0, 4), (1, 1), (1, 3), (2, 0), (2, 2), (2, 4), (3, 1), (3, 3), (4, 0), (4, 2), (4, 4)]リスト間で共通要素を見つける:
# 2つの数値リスト
list1 = [1, 2, 3, 4, 5]
list2 = [4, 5, 6, 7, 8]
# 値が等しい(共通要素の)ペアを見つける
common = [x for x in list1 for y in list2 if x == y]
print(common) # Output: [4, 5]注: 共通要素を見つける目的では、集合の積集合を使うほうが効率的です: set(list1) & set(list2)(17章で学びました)。
34.6.5) ネストした辞書内包表記
辞書内包表記でも複数の for 句を使えます。
# 座標の合計の辞書を作成
coord_sums = {(x, y): x + y for x in range(3) for y in range(3)}
print(coord_sums)
# Output: {(0, 0): 0, (0, 1): 1, (0, 2): 2, (1, 0): 1, (1, 1): 2, (1, 2): 3, (2, 0): 2, (2, 1): 3, (2, 2): 4}34.6.6) ネストした内包表記を避けるべきとき
ネストした内包表記は強力ですが、すぐに読みづらくなることがあります。次のガイドラインを参考にしてください。
許容範囲 - 比較的シンプル:
# 2段階のネスト、単純な式
matrix = [[i * j for j in range(3)] for i in range(3)]
print(matrix) # Output: [[0, 0, 0], [0, 1, 2], [0, 2, 4]]複雑になってきた - ループを検討:
# 3段階のネスト - 読みにくい
result = [[[i + j + k for k in range(2)] for j in range(2)] for i in range(2)]
# Better as nested loops for clarity目安: for 句が2つを超える場合、または式が複雑な場合は、代わりに従来のネストしたループを使いましょう。
# 明示的なループのほうが明確
result = []
for i in range(2):
middle = []
for j in range(2):
inner = []
for k in range(2):
inner.append(i + j + k)
middle.append(inner)
result.append(middle)複数の for 句を持つ内包表記は強力な道具ですが、忘れないでください。簡潔さよりも明確さが重要です。ネストした内包表記が理解しづらくなったら、明示的なループを使うほうがよいでしょう。