コンテンツにスキップ

リスト・タプル・辞書

この章で学ぶこと

  • リスト(list)の作成・アクセス・スライス・主要メソッド
  • タプル(tuple)の特徴と使いどころ
  • 辞書(dict)のキーと値の操作
  • 集合(set)の基本
  • リスト内包表記による効率的なリスト生成
  • コレクションに関するよくある間違い

コレクション型の比較

Python の主要なコレクション型を比較した表です。

表記 順序 重複 ミュータブル 主な用途
list [1, 2, 3] あり はい 汎用的な順序付きデータ
tuple (1, 2, 3) あり いいえ 変更不可のデータ、関数の複数戻り値
dict {"a": 1} あり* キーは不可 はい キーと値のペア
set {1, 2, 3} なし 不可 はい 重複除去、集合演算

(*) Python 3.7 以降、辞書は挿入順序を保持します。


リスト(list)

リストは、複数の値を順番に格納できるデータ構造です。Python で最もよく使われるコレクションです。

リストの作成

# リストの作成
fruits = ["りんご", "みかん", "ぶどう"]
numbers = [1, 2, 3, 4, 5]
empty_list = []  # 空のリスト

# 異なる型を混在させることも可能(ただし通常は同じ型で統一する)
mixed = [1, "hello", 3.14, True]

インデックスによるアクセス

リストの各要素にはインデックス(添字)でアクセスします。インデックスは 0 から始まることに注意してください。

fruits = ["りんご", "みかん", "ぶどう", "もも", "いちご"]

print(fruits[0])    # りんご(先頭の要素)
print(fruits[2])    # ぶどう(3 番目の要素)
print(fruits[-1])   # いちご(末尾の要素)
print(fruits[-2])   # もも(末尾から 2 番目)

よくある間違い

範囲外のインデックスにアクセスする:

fruits = ["りんご", "みかん", "ぶどう"]

# 間違い: 存在しないインデックス
# print(fruits[3])   # IndexError: list index out of range
# print(fruits[10])  # IndexError

# 正しい: インデックスは 0 から len(fruits)-1 まで
print(fruits[0])   # りんご
print(fruits[2])   # ぶどう(最後の要素)
print(fruits[-1])  # ぶどう(末尾の要素にアクセスする安全な方法)

リストの長さを確認してからアクセスすると安全です:

if len(fruits) > 3:
    print(fruits[3])
else:
    print("インデックス 3 は範囲外です")

要素の変更

リストはミュータブル(変更可能)なので、要素を後から変更できます。

fruits = ["りんご", "みかん", "ぶどう"]
fruits[1] = "バナナ"  # 2 番目の要素を変更
print(fruits)  # ['りんご', 'バナナ', 'ぶどう']

スライス

スライスを使うと、リストの一部を切り出すことができます。

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(numbers[2:5])    # [2, 3, 4](インデックス 2 から 4 まで)
print(numbers[:3])     # [0, 1, 2](先頭から 3 つ)
print(numbers[7:])     # [7, 8, 9](インデックス 7 から末尾まで)
print(numbers[::2])    # [0, 2, 4, 6, 8](2 つおきに取得)
print(numbers[::-1])   # [9, 8, 7, ..., 0](逆順)

スライスの書式

リスト[開始:終了:ステップ] の形式です。終了のインデックスの要素は含まれないことに注意してください。range() と同じ考え方です。

スライスは IndexError にならない

インデックスアクセス(lst[10])は範囲外でエラーになりますが、スライス(lst[10:20])は範囲外でもエラーにならず、空リストや利用可能な範囲の結果を返します。

>>> lst = [1, 2, 3]
>>> lst[10:20]
[]
>>> lst[1:100]
[2, 3]

リストの主要メソッド

fruits = ["りんご", "みかん"]

# append: 末尾に要素を追加
fruits.append("ぶどう")
print(fruits)  # ['りんご', 'みかん', 'ぶどう']

# insert: 指定位置に要素を挿入
fruits.insert(1, "バナナ")
print(fruits)  # ['りんご', 'バナナ', 'みかん', 'ぶどう']

# pop: 指定位置の要素を取り出して削除(デフォルトは末尾)
removed = fruits.pop()
print(removed)  # ぶどう
print(fruits)   # ['りんご', 'バナナ', 'みかん']

# remove: 値を指定して削除(最初に見つかったもの)
fruits.remove("バナナ")
print(fruits)  # ['りんご', 'みかん']

# index: 値のインデックスを取得
print(fruits.index("みかん"))  # 1

# len: リストの長さ(要素数)を取得
print(len(fruits))  # 2

# in: 要素が含まれているか確認
print("りんご" in fruits)  # True
print("もも" in fruits)    # False

よくある間違い

append と extend の混同:

# append: 要素を 1 つ追加する
a = [1, 2, 3]
a.append([4, 5])
print(a)  # [1, 2, 3, [4, 5]](リストがそのまま 1 要素として追加される)

# extend: 別のリストの要素をすべて追加する
b = [1, 2, 3]
b.extend([4, 5])
print(b)  # [1, 2, 3, 4, 5](要素が展開されて追加される)

+ 演算子と append の違い:

# + は新しいリストを作る(元のリストは変わらない)
a = [1, 2]
b = a + [3]
print(a)  # [1, 2](変わらない)
print(b)  # [1, 2, 3]

# append は元のリストを変更する(戻り値は None)
a = [1, 2]
result = a.append(3)
print(a)       # [1, 2, 3](変わる)
print(result)  # None(append は None を返す!)

ソート

numbers = [3, 1, 4, 1, 5, 9, 2, 6]

# sort: リストを直接並び替える(元のリストが変更される)
numbers.sort()
print(numbers)  # [1, 1, 2, 3, 4, 5, 6, 9]

# 降順にソート
numbers.sort(reverse=True)
print(numbers)  # [9, 6, 5, 4, 3, 2, 1, 1]

# sorted: 新しいリストを返す(元のリストは変更されない)
original = [3, 1, 4, 1, 5]
sorted_list = sorted(original)
print(original)     # [3, 1, 4, 1, 5](変更されない)
print(sorted_list)  # [1, 1, 3, 4, 5]

sort() と sorted() の使い分け

メソッド/関数 元のリスト 戻り値 使い場所
list.sort() 変更される None 元のリストを直接並べ替えたいとき
sorted(list) 変更されない 新しいリスト 元のリストを残したいとき

リストの結合とコピー

# リストの結合
a = [1, 2, 3]
b = [4, 5, 6]
c = a + b
print(c)  # [1, 2, 3, 4, 5, 6]

# extend: 別のリストの要素をすべて追加
a.extend(b)
print(a)  # [1, 2, 3, 4, 5, 6]

# リストのコピー
original = [1, 2, 3]
copy = original.copy()  # または list(original) や original[:]
copy.append(4)
print(original)  # [1, 2, 3](元のリストは変わらない)
print(copy)      # [1, 2, 3, 4]

よくある間違い

リストの代入はコピーではない(エイリアスの罠):

これは初心者が最も陥りやすい間違いの 1 つです。

# 間違い: = で代入すると、同じリストを共有する
a = [1, 2, 3]
b = a        # b は a と同じリストを参照している(コピーではない)
b.append(4)
print(a)     # [1, 2, 3, 4](a も変わってしまう!)
print(b)     # [1, 2, 3, 4]
print(a is b)  # True(同じオブジェクト)

# 正しい: コピーを作る
a = [1, 2, 3]
b = a.copy()   # 独立したコピーを作成
b.append(4)
print(a)     # [1, 2, 3](a は変わらない)
print(b)     # [1, 2, 3, 4]
print(a is b)  # False(別のオブジェクト)

コピーの方法は 3 通りあります:

original = [1, 2, 3]

copy1 = original.copy()    # 方法 1: copy メソッド
copy2 = list(original)     # 方法 2: list() コンストラクタ
copy3 = original[:]        # 方法 3: スライス

ただし、リストの中にリストがある場合(ネストされたリスト)は、copy モジュールの deepcopy が必要です:

import copy

nested = [[1, 2], [3, 4]]
shallow = nested.copy()       # 浅いコピー
deep = copy.deepcopy(nested)  # 深いコピー

shallow[0].append(99)
print(nested)   # [[1, 2, 99], [3, 4]](浅いコピーでは内部リストが共有される)
print(deep)     # [[1, 2], [3, 4]](深いコピーは完全に独立)

実行例

>>> fruits = ["りんご", "みかん", "ぶどう"]
>>> len(fruits)
3
>>> fruits.append("もも")
>>> fruits
['りんご', 'みかん', 'ぶどう', 'もも']
>>> fruits[1]
'みかん'
>>> fruits[-1]
'もも'
>>> "ぶどう" in fruits
True
>>> fruits.pop()
'もも'
>>> fruits
['りんご', 'みかん', 'ぶどう']

タプル(tuple)

タプルはリストに似ていますが、イミュータブル(変更不可)です。一度作成すると、要素の追加・変更・削除ができません。

タプルの作成

# 丸括弧で作成
point = (3, 5)
rgb = (255, 128, 0)

# 括弧を省略することもできる
coordinates = 10, 20

# 要素が 1 つのタプル(末尾にカンマが必要)
single = (42,)

よくある間違い

要素が 1 つのタプルでカンマを忘れる:

# 間違い: カンマがないとただの整数になる
not_a_tuple = (42)
print(type(not_a_tuple))  # <class 'int'>(タプルではない!)

# 正しい: カンマを忘れずにつける
single_tuple = (42,)
print(type(single_tuple))  # <class 'tuple'>

# 文字列でも同様
not_a_tuple = ("hello")
print(type(not_a_tuple))  # <class 'str'>

single_tuple = ("hello",)
print(type(single_tuple))  # <class 'tuple'>

タプルの操作

point = (3, 5, 7)

# インデックスでアクセス
print(point[0])  # 3
print(point[1])  # 5

# スライスも使える
print(point[1:])  # (5, 7)

# 長さ
print(len(point))  # 3

# アンパック(複数の変数に展開)
x, y, z = point
print(f"x={x}, y={y}, z={z}")  # x=3, y=5, z=7

リストとタプルの比較

特徴 リスト list タプル tuple
記法 [1, 2, 3] (1, 2, 3)
変更可能か はい(ミュータブル) いいえ(イミュータブル)
要素の追加・削除 可能 不可能
辞書のキーに使えるか いいえ はい
用途 要素が変わる可能性があるデータ 変わらないデータ(座標、RGB 等)

タプルの使いどころ

  • 変更されたくないデータ(座標、RGB 値など)を格納する場合
  • 関数から複数の値を返す場合(return a, b は実質タプルを返している)
  • 辞書のキーとして使う場合(リストは辞書のキーにできないが、タプルはできる)

辞書(dict)

辞書(ディクショナリ)は、キーのペアでデータを管理するデータ構造です。

辞書の作成

# 波括弧で作成
student = {
    "name": "太郎",
    "age": 20,
    "grade": "B"
}

# 空の辞書
empty_dict = {}

値へのアクセスと変更

student = {"name": "太郎", "age": 20, "grade": "B"}

# キーを指定して値を取得
print(student["name"])   # 太郎
print(student["age"])    # 20

# 値の変更
student["grade"] = "A"
print(student["grade"])  # A

# 新しいキーと値の追加
student["city"] = "横浜"
print(student)
# {'name': '太郎', 'age': 20, 'grade': 'A', 'city': '横浜'}

よくある間違い

存在しないキーにアクセスして KeyError:

student = {"name": "太郎", "age": 20}

# 間違い: 存在しないキーにアクセスするとエラー
# print(student["email"])  # KeyError: 'email'

# 正しい方法 1: get() メソッドを使う(キーがなければデフォルト値を返す)
print(student.get("email", "未登録"))  # 未登録

# 正しい方法 2: in でキーの存在を確認してからアクセス
if "email" in student:
    print(student["email"])
else:
    print("email は登録されていません")

get メソッドで安全にアクセス

get() メソッドは、キーが存在しない場合にデフォルト値を返すため、KeyError を避けられます。第 2 引数を省略すると None が返ります。

student = {"name": "太郎", "age": 20}

# get の第 2 引数はデフォルト値
print(student.get("name", "不明"))    # 太郎(キーが存在する場合)
print(student.get("email", "不明"))   # 不明(キーが存在しない場合)
print(student.get("email"))           # None(デフォルト値省略時)

辞書の主要メソッド

student = {"name": "太郎", "age": 20, "grade": "B"}

# keys: すべてのキーを取得
print(list(student.keys()))    # ['name', 'age', 'grade']

# values: すべての値を取得
print(list(student.values()))  # ['太郎', 20, 'B']

# items: キーと値のペアを取得
print(list(student.items()))   # [('name', '太郎'), ('age', 20), ('grade', 'B')]

# in: キーの存在確認
print("name" in student)   # True
print("email" in student)  # False

# del: キーと値のペアを削除
del student["grade"]
print(student)  # {'name': '太郎', 'age': 20}

# pop: キーを指定して値を取り出し、削除する
age = student.pop("age")
print(age)      # 20
print(student)  # {'name': '太郎'}

よくある間違い

辞書の in はキーを検索する(値ではない):

scores = {"数学": 85, "英語": 72}

# in はキーを検索する
print("数学" in scores)  # True(キーを検索)
print(85 in scores)      # False(85 はキーではなく値)

# 値を検索したい場合は .values() を使う
print(85 in scores.values())  # True

辞書のループ

scores = {"数学": 85, "英語": 72, "物理": 90}

# キーをループ
for subject in scores:
    print(subject)

# キーと値を同時にループ
for subject, score in scores.items():
    print(f"{subject}: {score}点")
# 出力:
# 数学: 85点
# 英語: 72点
# 物理: 90点

実行例

>>> d = {"a": 1, "b": 2, "c": 3}
>>> d["a"]
1
>>> d["d"] = 4
>>> d
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> "b" in d
True
>>> d.get("x", 0)
0
>>> list(d.keys())
['a', 'b', 'c', 'd']
>>> for k, v in d.items():
...     print(f"{k} -> {v}")
...
a -> 1
b -> 2
c -> 3
d -> 4

集合(set)

集合は、重複のない要素の集まりです。要素の順序は保証されません。

# 集合の作成
colors = {"赤", "青", "緑", "赤"}  # 重複は自動的に除去される
print(colors)  # {'赤', '青', '緑'}

# リストから重複を除去する
numbers = [1, 2, 2, 3, 3, 3, 4]
unique = set(numbers)
print(unique)       # {1, 2, 3, 4}
print(list(unique)) # [1, 2, 3, 4](リストに戻す)

# 集合の演算
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a | b)  # {1, 2, 3, 4, 5, 6}(和集合)
print(a & b)  # {3, 4}(積集合)
print(a - b)  # {1, 2}(差集合)

よくある間違い

空の集合を {} で作ろうとする:

# 間違い: {} は空の辞書になる
empty = {}
print(type(empty))  # <class 'dict'>(辞書!)

# 正しい: set() を使う
empty_set = set()
print(type(empty_set))  # <class 'set'>

リスト内包表記

リスト内包表記(list comprehension)は、リストを簡潔に生成するための構文です。

基本構文

# 通常の for ループで書く場合
squares = []
for x in range(5):
    squares.append(x ** 2)
print(squares)  # [0, 1, 4, 9, 16]

# リスト内包表記で書く場合(同じ結果を 1 行で)
squares = [x ** 2 for x in range(5)]
print(squares)  # [0, 1, 4, 9, 16]

条件付きリスト内包表記

# 偶数だけを取り出す
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = [x for x in numbers if x % 2 == 0]
print(evens)  # [2, 4, 6, 8, 10]

# 条件に応じて変換する
labels = ["偶数" if x % 2 == 0 else "奇数" for x in range(5)]
print(labels)  # ['偶数', '奇数', '偶数', '奇数', '偶数']

実践例

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

# 二次元リスト(3x3 の行列)を生成
matrix = [[0 for _ in range(3)] for _ in range(3)]
print(matrix)  # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

# 辞書内包表記
names = ["太郎", "花子", "次郎"]
scores = [85, 92, 78]
score_dict = {name: score for name, score in zip(names, scores)}
print(score_dict)  # {'太郎': 85, '花子': 92, '次郎': 78}

リスト内包表記の使いどころ

リスト内包表記は、シンプルな変換やフィルタリングに適しています。処理が複雑になる場合は、可読性を重視して通常の for ループを使いましょう。

# 良い: シンプルな変換
doubled = [x * 2 for x in numbers]

# 悪い: 複雑すぎるリスト内包表記(読みにくい)
# result = [f(x) if g(x) else h(x) for x in data if condition(x)]

# 複雑な処理は for ループで書く方が良い
result = []
for x in data:
    if condition(x):
        if g(x):
            result.append(f(x))
        else:
            result.append(h(x))

よくある間違いのまとめ

よくある間違い

1. リストの代入がコピーにならない:

a = [1, 2, 3]
b = a          # コピーではなく同じオブジェクトへの参照
b.append(4)
print(a)       # [1, 2, 3, 4](a も変わってしまう)

# 正しい:
b = a.copy()   # 独立したコピーを作る

2. ループ中にリストを変更する:

# 間違い
nums = [1, 2, 3, 4, 5]
for n in nums:
    if n % 2 == 0:
        nums.remove(n)
print(nums)  # 期待: [1, 3, 5] だが結果は予測不能

# 正しい: リスト内包表記で新しいリストを作る
nums = [1, 2, 3, 4, 5]
nums = [n for n in nums if n % 2 != 0]
print(nums)  # [1, 3, 5]

3. 辞書の KeyError:

d = {"name": "太郎"}

# 間違い
# print(d["age"])  # KeyError

# 正しい
print(d.get("age", "不明"))  # 不明

4. タプルの要素 1 つでカンマ忘れ:

t = (1)    # int 型の 1
t = (1,)   # タプル型の (1,)

5. append の戻り値を使おうとする:

# 間違い: append は None を返す
lst = [1, 2, 3]
new_lst = lst.append(4)  # new_lst は None!
print(new_lst)  # None

# 正しい: append は元のリストを変更する
lst = [1, 2, 3]
lst.append(4)
print(lst)  # [1, 2, 3, 4]

6. 空の集合を {} で作ろうとする:

# 間違い: {} は辞書
s = {}
print(type(s))  # <class 'dict'>

# 正しい: set() を使う
s = set()
print(type(s))  # <class 'set'>

まとめ

  • リストはミュータブルな順序付きコレクションで、append, pop, sort などのメソッドを持つ
  • インデックスは 0 から始まり、負のインデックスで末尾からアクセスできる
  • スライス [開始:終了:ステップ] でリストの一部を切り出せる
  • リストの代入(b = a)はコピーではなくエイリアス。独立したコピーには .copy() を使う
  • タプルはイミュータブルなリストであり、変更されないデータに適している。要素 1 つのタプルにはカンマが必要
  • 辞書はキーと値のペアでデータを管理し、キーで高速にアクセスできる。存在しないキーには get() を使う
  • 集合は重複のない要素の集まりで、和集合・積集合などの集合演算ができる。空の集合は set() で作る
  • リスト内包表記を使うと、リストの生成を簡潔に記述できる