リストとタプル(発展)

前回のレッスンでは、基本的なリスト操作を学びました。今回はより「Python らしい」概念を探求します——リスト内包表記(5 行分の処理を 1 行で)の魅力と、タプル(「イミュータブルなリスト」)とその使い分けについて。これであなたのコードはますます本物の Python プログラマーのものになっていくでしょう。


1. 高度なリストソート

レッスン 10 では、sort()sorted() の基本を学びました。これらは key パラメータも受け入れ、「どのルールでソートするか」を指定できます。

PYTHON
# 文字列の長さでソート
words = ["Python", "Java", "JavaScript", "C", "Go"]
sorted_by_len = sorted(words, key=len)
print(sorted_by_len)          # ['C', 'Go', 'Java', 'Python', 'JavaScript']

# 最後の文字でソート
words = ["banana", "apple", "cherry", "date"]
sorted_by_last = sorted(words, key=lambda w: w[-1])
print(sorted_by_last)         # ['banana', 'date', 'apple', 'cherry']

# タプルの特定の要素でソート
scores = [("Zhang San", 85), ("Li Si", 92), ("Wang Wu", 78)]
sorted_scores = sorted(scores, key=lambda s: s[1], reverse=True)
print(sorted_scores)          # [('Li Si', 92), ('Zhang San', 85), ('Wang Wu', 78)]
💡 key パラメータは非常に便利です —— 1 つの引数を受け取り値を返す任意の関数を渡せます。sorted() はこの戻り値をソートに使用します。上記の lambda は無名関数で、後で詳しく説明します。

例:リーダーボードの生成(難易度 ⭐⭐)

PYTHON
# ゲームのリーダーボード — スコア降順でソート
players = [
    ("Player 1", 3500),
    ("Player 2", 5200),
    ("Player 3", 2800),
    ("Player 4", 4100),
]

# スコア降順でソート
ranked = sorted(players, key=lambda p: p[1], reverse=True)

print("=== Leaderboard ===")
for i, (name, score) in enumerate(ranked, 1):
    print(f"#{i}: {name} — {score} points")
▶ 試してみよう

出力:

TEXT
=== Leaderboard ===
#1: Player 2 — 5200 points
#2: Player 4 — 4100 points
#3: Player 1 — 3500 points
#4: Player 3 — 2800 points

2. リスト内包表記

リスト内包表記は、Python で最も愛されている機能の 1 つです——たった 1 行のコードで新しいリストを生成できます。

基本構文

PYTHON
[expression for variable in iterable]

従来の方法 vs 内包表記

PYTHON
# 0〜9 の平方を生成 — 従来の方法
squares = []
for i in range(10):
    squares.append(i ** 2)
print(squares)                # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# リスト内包表記を使用 — 1 行
squares = [i ** 2 for i in range(10)]
print(squares)                # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

条件付き内包表記

PYTHON
# 偶数のみ抽出
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = [n for n in numbers if n % 2 == 0]
print(evens)                  # [2, 4, 6, 8, 10]

# 文字列を大文字に変換
words = ["hello", "world", "python"]
upper_words = [w.upper() for w in words]
print(upper_words)            # ['HELLO', 'WORLD', 'PYTHON']

# 長さ 5 以上の単語を抽出
words = ["cat", "elephant", "dog", "giraffe", "ant"]
long_words = [w for w in words if len(w) >= 5]
print(long_words)             # ['elephant', 'giraffe']
💡 内包表記は簡潔ですが、使いすぎないでください。 ロジックが複雑な場合(ネストしたループや複数の条件など)は、通常の for ループの方が読みやすいです。簡潔さは可読性と同じではありません。

例:データ処理(難易度 ⭐⭐)

PYTHON
# 生データ — 空の値や余分な空白を含む可能性がある
raw_data = ["  Zhang San  ", "", "  Li Si  ", "  Wang Wu  ", "", "  Zhao Liu  "]

# 内包表記で 1 ステップクリーニング:strip + 空のフィルタリング
cleaned = [name.strip() for name in raw_data if name.strip()]
print(cleaned)                # ['Zhang San', 'Li Si', 'Wang Wu', 'Zhao Liu']

# 数値処理
temps_c = [0, 10, 20, 30, 40]
temps_f = [c * 9 / 5 + 32 for c in temps_c]
print(temps_f)                # [32.0, 50.0, 68.0, 86.0, 104.0]
▶ 試してみよう

3. タプルとは

タプルは括弧 () で定義します。リストと似ていますが、重要な違いがあります——タプルはイミュータブルです。一度作成すると変更できません。

PYTHON
# タプルを定義
point = (3, 5)
color = (255, 0, 0)
empty = ()
single = (42,)          # 注意:1 要素のタプルにはカンマが必要!

print(point)            # (3, 5)
print(type(point))      # <class 'tuple'>

タプルはイミュータブル

PYTHON
point = (3, 5)
# point[0] = 10   ← エラー!タプルは変更できない
print(point[0])         # 3 — 読み取りは可能、変更は不可

なぜタプルが必要か

イミュータビリティには 2 つの利点があります:

1. 辞書のキーに使用できる(リストはミュータブルのため不可)

PYTHON
# タプルは辞書のキーにできる
locations = {
    (35.68, 139.76): "Tokyo",
    (31.23, 121.47): "Shanghai",
}
print(locations[(35.68, 139.76)])   # Tokyo

# リストはキーにできない
# d = {[1, 2]: "error"}    ← エラーになる!

2. 変更すべきでないデータを表現する

PYTHON
# 座標、色、設定 — 「変わらない」データはタプルに適している
RGB_RED = (255, 0, 0)        # 赤色の値は変更すべきでない
DEFAULT_CONFIG = ("localhost", 8080, "admin")
💡 タプルとリストはいつ使い分けるか? 簡単なルール:データの変更が必要ならリスト、不要ならタプルを使います。 複数の値を返す関数は通常タプルを使います。


4. タプルのアンパック

タプルの最も便利な機能の 1 つ——アンパック。タプルの要素を複数の変数に直接代入します:

PYTHON
point = (3, 5)
x, y = point               # アンパック
print(x)                   # 3
print(y)                   # 5

# 変数のスワップ — 本質的にタプルのアンパック
a, b = 10, 20
a, b = b, a                # 右辺でタプル (20, 10) を構築し、アンパック
print(a, b)                # 20 10

# 関数が複数の値を返す
def get_min_max(numbers):
    return min(numbers), max(numbers)

result = get_min_max([3, 1, 4, 1, 5])
print(result)              # (1, 5)

low, high = get_min_max([3, 1, 4, 1, 5])
print(f"Min: {low}, Max: {high}")   # Min: 1, Max: 5

_ で不要な値を無視

PYTHON
data = ("Zhang San", 25, "Beijing", "Engineer")
name, _, city, _ = data     # _ は「この値は気にしない」という意味
print(f"{name} lives in {city}")  # Zhang San lives in Beijing

例:座標計算(難易度 ⭐⭐)

PYTHON
# 2 点を定義
p1 = (1, 2)
p2 = (4, 6)

# 距離計算のためにアンパック
x1, y1 = p1
x2, y2 = p2

distance = ((x2 - x1)  2 + (y2 - y1)  2) ** 0.5
print(f"Distance: {distance:.2f}")     # 5.00

# enumerate でインデックス付き反復
fruits = ["apple", "banana", "orange"]
for i, fruit in enumerate(fruits, 1):
    print(f"{i}. {fruit}")
▶ 試してみよう

出力:

TEXT
Distance: 5.00
1. apple
2. banana
3. orange

5. リスト vs タプル選択ガイド

機能 リスト タプル
ミュータブル ✅ 追加/削除/変更可能 ❌ イミュータブル
速度 やや遅い やや速い
メモリ やや大きい やや小さい
辞書のキーにできる
使用場面 頻繁に変更されるデータ 固定された変更不可のデータ
典型的な用途 ショッピングカート、To-Do リスト、動的データ 座標、色、関数の戻り値
💡 実用的な習慣: どちらを使うか迷ったら、リストから始めましょう。リストはほとんどのシナリオでより柔軟です。一部のデータが本当に変更されるべきでないとわかったら、タプルに切り替えてください。


よくあるユースケース


❓ よくある質問

Q リスト内包表記と通常の for ループ、どちらが速いですか?
A リスト内包表記は通常高速です。ループを Python レベルではなく C レベルで実行するためです。ただし、その差は大規模データ(数万項目)でのみ顕著です。日常のコーディングでは、まず可読性を優先しましょう。
Q なぜ 1 要素のタプルにはカンマが必要なのですか?(42)(42,) の違いは何ですか?
A (42) の括弧は数学的なグループ化として解釈され、タプルではありません——type((42))<class 'int'> を返します。(42,) はタプルです。したがって、1 要素のタプルにはカンマを含める必要があります:(42,)
Q sorted()key パラメータはどのように機能しますか?実用的な例を教えてください。
A 最も一般的なシナリオは特定の属性でソートすることです。例えば、文字列の長さでソート:sorted(words, key=len)。リスト内のタプルの 2 番目の要素でソート:sorted(data, key=lambda x: x[1])。辞書の値でソート:sorted(dict.items(), key=lambda x: x[1])。重要なのは、key が各要素をソートキーにマッピングする関数を受け入れることを理解することです。

📖 まとめ


📝 練習問題

  1. 初級(難易度 ⭐):リスト内包表記を使って、1 から 20 までの 3 で割り切れる数の平方を生成してください。ヒント: [expression for n in range(...) if condition]

  2. 中級(難易度 ⭐⭐):名前リスト names = [" alice ", "BOB", " Charlie", " david "] が与えられたとき、リスト内包表記で 1 ステップで:空白の除去 + 最初の文字を大文字、残りを小文字にしてください。ヒント: strip() で空白除去、title() または capitalize() で大文字小文字変換。

  3. 上級(難易度 ⭐⭐⭐):関数 analyze_numbers(*args) を書いてください。任意の数の数値引数を受け取り、タプル (max, min, average, count_above_average) を返します。ヒント: 可変長引数には *args を使用。平均以上の値をカウントするにはリスト内包表記を使用。

100%