メインコンテンツへスキップ
Lesson2 / 5

タプルの使い所

目次

1. このレッスンで学ぶこと

  • タプルが適している場面
  • リストとの使い分け
  • タプルの実践的な活用方法
  • namedtupleの紹介

2. タプルとは

タプルは、複数の値を順序付きで格納する、変更不可能(イミュータブル)なデータ構造です。

項目説明
書き方(要素1, 要素2, 要素3, ...)
変更不可能
順序あり
用途固定データ、複数戻り値、辞書のキー

主な特徴:

  • 丸括弧 () で囲んで作成される
  • 一度作成すると変更できない(イミュータブル)
  • インデックスとスライスでアクセス可能
  • 辞書のキーとして使用できる(リストは不可)

簡単なコード例:

Python
# 固定データの定義
RGB_RED = (255, 0, 0)
RGB_GREEN = (0, 255, 0)

print(f"赤: {RGB_RED}")
print(f"第1要素: {RGB_RED[0]}")

# 複数戻り値
def get_dimensions():
    return 640, 480  # タプルで返される

width, height = get_dimensions()
print(f"画面サイズ: {width}x{height}")

3. なぜタプルを使うのか?

タプルには変更不可という特性があり、特定の状況で非常に有用です。

Python
# 座標は変更されるべきでない
ORIGIN = (0, 0)

# RGB色は固定値
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

# 設定値は保護したい
DATABASE_CONFIG = ("localhost", 5432, "mydb", "user")

タプルを使うことで、データの整合性を保てます。

💡 豆知識: Pythonの内部では、タプルはリストよりも効率的に実装されています。ハッシュ可能なので辞書のキーにでき、メモリ使用量も少なくて済みます。


4. タプルが適している場面

1. 固定データ・定数

機能: 変更されるべきでないデータを保護します。

書き方:

Python
定数 = (1,2, ...)

用途: 設定値、座標、RGB色、曜日

注意点: 大文字の変数名を使うと定数であることが明確

Python
# 曜日(変更されない)
WEEKDAYS = ("月", "火", "水", "木", "金", "土", "日")

# RGB色の定義
COLORS = {
    "赤": (255, 0, 0),
    "緑": (0, 255, 0),
    "青": (0, 0, 255),
    "白": (255, 255, 255),
    "黒": (0, 0, 0)
}

# 今日は何曜日?
day_index = 2  # 水曜日
print(f"今日は{WEEKDAYS[day_index]}曜日です")

# 色の取得
color = COLORS["赤"]
r, g, b = color
print(f"赤色のRGB: R={r}, G={g}, B={b}")

実行結果:

今日は水曜日です
赤色のRGB: R=255, G=0, B=0

2. 関数の複数戻り値

機能: 関数から複数の値をまとめて返します。

書き方:

Python
def 関数():
    return1,2,3

用途: 計算結果の複数値、統計値、座標計算

注意点: 戻り値が多すぎると読みにくい(3-4個まで)

Python
def calculate_rectangle(width, height):
    """長方形の面積と周囲の長さを計算"""
    area = width * height
    perimeter = 2 * (width + height)
    return area, perimeter

# 使用例
w, h = 5, 3
area, perimeter = calculate_rectangle(w, h)
print(f"幅{w}, 高さ{h}の長方形")
print(f"  面積: {area}")
print(f"  周囲: {perimeter}")

# 統計値を返す
def get_statistics(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

data = [85, 92, 78, 95, 88]
min_val, max_val, avg = get_statistics(data)
print(f"\n統計:")
print(f"  最小: {min_val}")
print(f"  最大: {max_val}")
print(f"  平均: {avg:.2f}")

実行結果:

幅5, 高さ3の長方形
  面積: 15
  周囲: 16

統計:
  最小: 78
  最大: 95
  平均: 87.60

3. 辞書のキー

機能: タプルは辞書のキーとして使えます(リストは不可)。

書き方:

Python
辞書 = {(キー1, キー2):}

用途: 2次元座標のマッピング、複合キー

注意点: タプル内の要素もイミュータブルである必要がある

Python
# 座標をキーにした地図
world_map = {
    (0, 0): "スタート地点",
    (1, 0): "森",
    (2, 0): "村",
    (0, 1): "山",
    (1, 1): "湖",
    (2, 1): "城"
}

# 座標で検索
position = (1, 1)
print(f"座標{position}: {world_map[position]}")

# 全体を表示
print("\n=== 地図 ===")
for (x, y), location in world_map.items():
    print(f"({x},{y}): {location}")

# 複合キーの例(姓・名)
phonebook = {
    ("山田", "太郎"): "090-1234-5678",
    ("佐藤", "花子"): "090-2345-6789",
    ("山田", "花子"): "090-3456-7890"
}

name = ("山田", "太郎")
print(f"\n{name[0]} {name[1]}さんの電話番号: {phonebook[name]}")

実行結果:

座標(1, 1): 湖

=== 地図 ===
(0,0): スタート地点
(1,0): 森
(2,0): 村
(0,1): 山
(1,1): 湖
(2,1): 城

山田 太郎さんの電話番号: 090-1234-5678

4. データベースのレコード

Python
# データベースから取得したレコード(タプルで表現)
users = [
    (1, "yamada", "yamada@example.com", 28),
    (2, "tanaka", "tanaka@example.com", 35),
    (3, "sato", "sato@example.com", 42)
]

print("=== ユーザー一覧 ===")
for user in users:
    id, username, email, age = user
    print(f"ID:{id} {username} ({age}歳) - {email}")

# 特定条件の検索
print("\n30歳以上:")
for user in users:
    id, username, email, age = user
    if age >= 30:
        print(f"  {username} ({age}歳)")

実行結果:

=== ユーザー一覧 ===
ID:1 yamada (28歳) - yamada@example.com
ID:2 tanaka (35歳) - tanaka@example.com
ID:3 sato (42歳) - sato@example.com

30歳以上:
  tanaka (35歳)
  sato (42歳)

5. 一時的なデータのグループ化

Python
# CSVファイルの行データ
csv_data = [
    ("商品A", 1000, 10),
    ("商品B", 1500, 5),
    ("商品C", 800, 20)
]

print("=== 在庫一覧 ===")
total_value = 0
for row in csv_data:
    name, price, quantity = row
    value = price * quantity
    total_value += value
    print(f"{name}: {price}円 × {quantity}個 = {value}円")

print(f"\n在庫総額: {total_value}円")

実行結果:

=== 在庫一覧 ===
商品A: 1000円 × 10個 = 10000円
商品B: 1500円 × 5個 = 7500円
商品C: 800円 × 20個 = 16000円

在庫総額: 33500円

5. リストとの使い分け

タプルを使うべき場合

Python
# ケース1: データが変更されない
MONTHS = ("1月", "2月", "3月", "4月", "5月", "6月",
          "7月", "8月", "9月", "10月", "11月", "12月")

# ケース2: 関数の戻り値
def get_min_max(numbers):
    return min(numbers), max(numbers)

# ケース3: 辞書のキー
cache = {
    ("add", 1, 2): 3,
    ("mul", 3, 4): 12
}

# ケース4: 構造化データ
person = ("太郎", 25, "東京")

リストを使うべき場合

Python
# ケース1: データが変更される
shopping_cart = ["りんご", "バナナ"]
shopping_cart.append("オレンジ")  # 追加

# ケース2: 同じ種類のデータの集まり
scores = [85, 92, 78, 95]
scores.sort()  # ソート

# ケース3: 要素の追加・削除が頻繁
todo_list = ["メール確認", "資料作成"]
todo_list.remove("メール確認")  # 完了

# ケース4: 同じ型の繰り返しデータ
temperatures = [22.5, 23.1, 24.0, 23.8]

6. namedtupleの活用

namedtupleとは

機能: 名前付きフィールドを持つタプルのサブクラスを作成します。

書き方:

Python
from collections import namedtuple
クラス名 = namedtuple('クラス名', ['フィールド1', 'フィールド2'])

用途: 構造化データ、読みやすいコード、軽量なクラス

注意点: イミュータブル、メソッドは追加できない

Python
from collections import namedtuple

# Pointクラスの定義
Point = namedtuple('Point', ['x', 'y'])

# インスタンスの作成
p1 = Point(10, 20)
p2 = Point(x=30, y=40)

# アクセス方法
print(f"p1: x={p1.x}, y={p1.y}")
print(f"p2: x={p2.x}, y={p2.y}")

# インデックスでもアクセス可能
print(f"p1[0]={p1[0]}, p1[1]={p1[1]}")

# アンパックも可能
x, y = p1
print(f"アンパック: x={x}, y={y}")

実行結果:

p1: x=10, y=20
p2: x=30, y=40
p1[0]=10, p1[1]=11
アンパック: x=10, y=20

namedtupleの実用例

Python
from collections import namedtuple

# 社員データ
Employee = namedtuple('Employee', ['id', 'name', 'department', 'salary'])

employees = [
    Employee(1, "山田太郎", "営業", 400000),
    Employee(2, "佐藤花子", "開発", 450000),
    Employee(3, "鈴木一郎", "人事", 380000)
]

print("=== 社員リスト ===")
for emp in employees:
    print(f"ID:{emp.id} {emp.name} ({emp.department}) - {emp.salary:,}円")

# 部署でグループ化
departments = {}
for emp in employees:
    if emp.department not in departments:
        departments[emp.department] = []
    departments[emp.department].append(emp)

print("\n=== 部署別 ===")
for dept, members in departments.items():
    print(f"{dept}部:")
    for emp in members:
        print(f"  {emp.name}")

実行結果:

=== 社員リスト ===
ID:1 山田太郎 (営業) - 400,000円
ID:2 佐藤花子 (開発) - 450,000円
ID:3 鈴木一郎 (人事) - 380,000円

=== 部署別 ===
営業部:
  山田太郎
開発部:
  佐藤花子
人事部:
  鈴木一郎

7. 具体例

例1: ゲームの座標管理

Python
# プレイヤーの位置
player_pos = (5, 3)
enemy_positions = [(2, 1), (8, 4), (6, 7)]

# 距離計算
def manhattan_distance(pos1, pos2):
    x1, y1 = pos1
    x2, y2 = pos2
    return abs(x1 - x2) + abs(y1 - y2)

print(f"プレイヤー位置: {player_pos}")
print("\n敵との距離:")
for i, enemy_pos in enumerate(enemy_positions, 1):
    dist = manhattan_distance(player_pos, enemy_pos)
    print(f"  敵{i} {enemy_pos}: 距離{dist}")

実行結果:

プレイヤー位置: (5, 3)

敵との距離:
  敵1 (2, 1): 距離5
  敵2 (8, 4): 距離4
  敵3 (6, 7): 距離5

例2: 設定ファイルの管理

Python
# アプリケーション設定
class Config:
    # データベース設定
    DATABASE = ("localhost", 5432, "myapp", "user", "password")

    # サーバー設定
    SERVER = ("0.0.0.0", 8000, True)  # host, port, debug

    # ログ設定
    LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR")

# 設定の使用
db_host, db_port, db_name, db_user, db_pass = Config.DATABASE
print(f"データベース接続: {db_user}@{db_host}:{db_port}/{db_name}")

server_host, server_port, debug = Config.SERVER
mode = "デバッグ" if debug else "本番"
print(f"サーバー: {server_host}:{server_port} ({mode}モード)")

実行結果:

データベース接続: user@localhost:5432/myapp
サーバー: 0.0.0.0:8000 (デバッグモード)

例3: 状態遷移の定義

Python
# 状態遷移テーブル(現在の状態、入力、次の状態)
transitions = [
    ("待機", "スタート", "実行中"),
    ("実行中", "一時停止", "一時停止中"),
    ("一時停止中", "再開", "実行中"),
    ("実行中", "完了", "完了"),
    ("一時停止中", "キャンセル", "待機")
]

def get_next_state(current, action):
    for curr, act, next in transitions:
        if curr == current and act == action:
            return next
    return None

# 状態遷移のシミュレーション
state = "待機"
actions = ["スタート", "一時停止", "再開", "完了"]

print(f"初期状態: {state}")
for action in actions:
    next_state = get_next_state(state, action)
    if next_state:
        print(f"{action}{next_state}")
        state = next_state
    else:
        print(f"{action} → 無効な操作")

実行結果:

初期状態: 待機
スタート → 実行中
一時停止 → 一時停止中
再開 → 実行中
完了 → 完了

8. まとめ

このレッスンでは、タプルの実践的な活用方法を学びました。

  • 座標・設定値・複数戻り値など、固定データの表現に使えると理解しました。
  • タプルを使うことで、値の不意な変更を防げます。
  • 分割代入と組み合わせると、読みやすく扱いやすいコードになります。
  • namedtupleのような拡張的な使い方の入口を確認しました。
  • データの性質に合わせて型を選ぶことの重要性を理解しました。