リストとタプル(発展)
前回のレッスンでは、基本的なリスト操作を学びました。今回はより「Python らしい」概念を探求します——リスト内包表記(5 行分の処理を 1 行で)の魅力と、タプル(「イミュータブルなリスト」)とその使い分けについて。これであなたのコードはますます本物の Python プログラマーのものになっていくでしょう。
1. 高度なリストソート
レッスン 10 では、sort() と sorted() の基本を学びました。これらは key パラメータも受け入れ、「どのルールでソートするか」を指定できます。
# 文字列の長さでソート
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 は無名関数で、後で詳しく説明します。
例:リーダーボードの生成(難易度 ⭐⭐)
# ゲームのリーダーボード — スコア降順でソート
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")
出力:
=== 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 行のコードで新しいリストを生成できます。
基本構文
[expression for variable in iterable]
従来の方法 vs 内包表記
# 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]
条件付き内包表記
# 偶数のみ抽出
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']
例:データ処理(難易度 ⭐⭐)
# 生データ — 空の値や余分な空白を含む可能性がある
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. タプルとは
タプルは括弧 () で定義します。リストと似ていますが、重要な違いがあります——タプルはイミュータブルです。一度作成すると変更できません。
# タプルを定義
point = (3, 5)
color = (255, 0, 0)
empty = ()
single = (42,) # 注意:1 要素のタプルにはカンマが必要!
print(point) # (3, 5)
print(type(point)) # <class 'tuple'>
タプルはイミュータブル
point = (3, 5)
# point[0] = 10 ← エラー!タプルは変更できない
print(point[0]) # 3 — 読み取りは可能、変更は不可
なぜタプルが必要か
イミュータビリティには 2 つの利点があります:
1. 辞書のキーに使用できる(リストはミュータブルのため不可)
# タプルは辞書のキーにできる
locations = {
(35.68, 139.76): "Tokyo",
(31.23, 121.47): "Shanghai",
}
print(locations[(35.68, 139.76)]) # Tokyo
# リストはキーにできない
# d = {[1, 2]: "error"} ← エラーになる!
2. 変更すべきでないデータを表現する
# 座標、色、設定 — 「変わらない」データはタプルに適している
RGB_RED = (255, 0, 0) # 赤色の値は変更すべきでない
DEFAULT_CONFIG = ("localhost", 8080, "admin")
4. タプルのアンパック
タプルの最も便利な機能の 1 つ——アンパック。タプルの要素を複数の変数に直接代入します:
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
_ で不要な値を無視
data = ("Zhang San", 25, "Beijing", "Engineer")
name, _, city, _ = data # _ は「この値は気にしない」という意味
print(f"{name} lives in {city}") # Zhang San lives in Beijing
例:座標計算(難易度 ⭐⭐)
# 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}")
出力:
Distance: 5.00
1. apple
2. banana
3. orange
5. リスト vs タプル選択ガイド
| 機能 | リスト | タプル |
|---|---|---|
| ミュータブル | ✅ 追加/削除/変更可能 | ❌ イミュータブル |
| 速度 | やや遅い | やや速い |
| メモリ | やや大きい | やや小さい |
| 辞書のキーにできる | ❌ | ✅ |
| 使用場面 | 頻繁に変更されるデータ | 固定された変更不可のデータ |
| 典型的な用途 | ショッピングカート、To-Do リスト、動的データ | 座標、色、関数の戻り値 |
よくあるユースケース
- リーダーボードのソート:
sorted(key=lambda ...)で複雑なデータをカスタムルールでソート - データクリーニング:リスト内包表記でフィルタリング、変換、クリーニングを 1 行で
- 座標と幾何学:点、長方形、RGB 色などにタプルを使用
- 複数戻り値関数:関数が
(min, max)タプルを返し、呼び出し側が直接アンパック - 列挙反復:
enumerate()は(index, element)タプルを返し、直接アンパック
❓ よくある質問
(42) と (42,) の違いは何ですか?(42) の括弧は数学的なグループ化として解釈され、タプルではありません——type((42)) は <class 'int'> を返します。(42,) はタプルです。したがって、1 要素のタプルにはカンマを含める必要があります:(42,)。sorted() の key パラメータはどのように機能しますか?実用的な例を教えてください。sorted(words, key=len)。リスト内のタプルの 2 番目の要素でソート:sorted(data, key=lambda x: x[1])。辞書の値でソート:sorted(dict.items(), key=lambda x: x[1])。重要なのは、key が各要素をソートキーにマッピングする関数を受け入れることを理解することです。📖 まとめ
sorted()のkeyパラメータでソートルールを指定。lambdaと組み合わせて非常に柔軟- リスト内包表記
[expression for variable in iterable if condition]で新しいリストを 1 行で作成 - タプルは
()構文を使用し、イミュータブルで辞書のキーに使用可能 - 1 要素のタプルにはカンマが必要:
(42,)—— そうしないと整数になる - タプルのアンパックで複数の値を複数の変数に代入。変数スワップもこれを活用
- ミュータブルなデータにはリスト、固定データにはタプル
📝 練習問題
-
初級(難易度 ⭐):リスト内包表記を使って、1 から 20 までの 3 で割り切れる数の平方を生成してください。ヒント:
[expression for n in range(...) if condition] -
中級(難易度 ⭐⭐):名前リスト
names = [" alice ", "BOB", " Charlie", " david "]が与えられたとき、リスト内包表記で 1 ステップで:空白の除去 + 最初の文字を大文字、残りを小文字にしてください。ヒント:strip()で空白除去、title()またはcapitalize()で大文字小文字変換。 -
上級(難易度 ⭐⭐⭐):関数
analyze_numbers(*args)を書いてください。任意の数の数値引数を受け取り、タプル(max, min, average, count_above_average)を返します。ヒント: 可変長引数には*argsを使用。平均以上の値をカウントするにはリスト内包表記を使用。



