14. リスト(list): 順序付きアイテムのコレクション
ここまで本書では、単一の数値、文字列、真偽値といった個々のデータを扱ってきました。ですが実際のプログラムでは、関連する項目の集まり—生徒名のリスト、温度測定値の系列、商品の価格の集まり、ユーザーコマンドの並び—を扱う必要があることがよくあります。Python の リスト(list) は、順序付きのデータコレクションを保存して扱うための基本ツールです。
リストは、複数の項目を特定の順序で保持できる シーケンス(sequence) です。文字列(文字しか含められない)とは異なり、リストには数値、文字列、真偽値、さらには別のリストなど、あらゆる種類のデータを入れられます。リストは ミュータブル(mutable) でもあり、作成後に中身を変更できます—項目の追加、項目の削除、既存項目の修正が可能です。
この章では、リストの作成方法、要素へのアクセス、変更方法、そして実用的なプログラミング問題を解くための使い方を探っていきます。最後には、リストが Python の中でも特に強力で頻繁に使われるデータ構造の1つである理由が理解できるようになります。
14.1) リストの作成と要素へのアクセス
14.1.1) 角括弧でリストを作る
リストを作る最も一般的な方法は、項目を 角括弧(square brackets) [] で囲み、項目同士をカンマで区切ることです。簡単な例を示します。
# 生徒名のリスト
students = ["Alice", "Bob", "Charlie", "Diana"]
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']Python がリストをどのように表示するかに注目してください。角括弧が表示され、各文字列の両側に引用符が付きます。これはリストの 表現(representation)、つまり Python が中身をどのように見せるかです。
リストにはどのような種類のデータでも入れられます。これはテストの点数のリストです。
# 整数の点数のリスト
scores = [85, 92, 78, 95, 88]
print(scores) # Output: [85, 92, 78, 95, 88]実用上はあまり一般的ではありませんが、同じリストに異なる型を混在させることもできます。
# 混在型のリスト(あまり一般的ではありませんが有効です)
mixed_data = ["Alice", 25, True, 3.14]
print(mixed_data) # Output: ['Alice', 25, True, 3.14]空のリスト(empty list) は項目を何も含まず、角括弧だけで作成します。
# 空のリスト
empty = []
print(empty) # Output: []
print(len(empty)) # Output: 0文字列で使ってきた len() 関数はリストにも使えます。リスト内の項目数を返します。
14.1.2) リストの順序と位置を理解する
リストは、項目を追加した 順序(order) を維持します。最初に入れた項目は最初のまま、2番目は2番目のまま、といった具合です。この順序付けは重要で、項目を 位置(position)(インデックス(index) とも呼ばれます)で指定してアクセスできるからです。
Python は 0始まりのインデックス(zero-based indexing) を使います。最初の項目は位置 0、2番目は位置 1、と続きます。最初は不思議に思えるかもしれませんが、多くのプログラミング言語で使われる慣習です。
実際にどのように動くか見てみましょう。
students = ["Alice", "Bob", "Charlie", "Diana"]
# 最初の生徒にアクセス(インデックス 0)
first_student = students[0]
print(first_student) # Output: Alice
# 3番目の生徒にアクセス(インデックス 2)
third_student = students[2]
print(third_student) # Output: Charlie3番目の生徒を取得するのに、インデックス 3 ではなく 2 を使っていることに注目してください。これは数え始めが 0 だからです。
14.1.3) 正のインデックスで要素にアクセスする
リスト要素にアクセスするには、リスト名の後ろに角括弧でインデックスを書きます: list_name[index]。インデックスは有効範囲内(0 から len(list) - 1)の整数である必要があります。
商品の価格を扱う実用例を示します。
# ドルでの商品の価格
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
# 特定の価格にアクセス
first_price = prices[0]
last_index = len(prices) - 1 # 最後の有効なインデックスを計算
last_price = prices[last_index]
print(f"First product costs: ${first_price}") # Output: First product costs: $19.99
print(f"Last product costs: ${last_price}") # Output: Last product costs: $8.99なぜ最後のインデックスに len(prices) - 1 を使うのでしょうか。リストに 5 個の項目がある場合、インデックスは 0, 1, 2, 3, 4 であり、最後の有効インデックスは常に長さより 1 小さいからです。
インデックスは式や計算にも使えます。
scores = [85, 92, 78, 95, 88]
# 最初の3つの点数の平均を計算
first_three_average = (scores[0] + scores[1] + scores[2]) / 3
print(f"Average of first three: {first_three_average}") # Output: Average of first three: 85.014.1.4) 負のインデックス: 末尾から数える
Python には便利な機能があります。負のインデックス(negative indices) を使うと、リストの末尾から項目にアクセスできます。インデックス -1 は最後の項目、-2 は最後から2番目、というように続きます。
students = ["Alice", "Bob", "Charlie", "Diana"]
# 末尾からアクセス
last_student = students[-1]
second_to_last = students[-2]
print(last_student) # Output: Diana
print(second_to_last) # Output: Charlieこれは、最後の項目が欲しいけれど len(list) - 1 を計算したくないときに特に便利です。
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
# 次の2つの方法は同じです
last_price_method1 = prices[len(prices) - 1]
last_price_method2 = prices[-1]
print(last_price_method1) # Output: 8.99
print(last_price_method2) # Output: 8.99正のインデックスと負のインデックスが同じ項目に対応する様子は次の通りです。
14.1.5) 無効なインデックスを使うとどうなるか
存在しないインデックスにアクセスしようとすると、Python は IndexError を発生させます。
students = ["Alice", "Bob", "Charlie"]
# WARNING: このリストのインデックスは 0, 1, 2(または -3, -2, -1)です - デモ用のみ
# インデックス 3 にアクセスしようとするとエラーになります
# PROBLEM: 3つの要素しかないリストにインデックス 3 は存在しません
# print(students[3]) # IndexError: list index out of rangeこのエラーは、そこに存在しない項目を要求したことを Python が知らせているのです。
14.2) リストのインデックス指定とスライス
14.2.1) リストのスライスの基本を理解する
第5章で学んだように文字列をスライスできるのと同様に、リストも スライス(slice) して一部分を取り出せます。スライスは、元のリスト要素の一部を含む新しいリストを作成します。構文は list[start:stop] で、start はスライス開始のインデックス(含む)、stop は終了の位置(含まない)です。
numbers = [10, 20, 30, 40, 50, 60, 70]
# インデックス 1 から(ただし)インデックス 4 の手前までを取得
subset = numbers[1:4]
print(subset) # Output: [20, 30, 40]スライス [1:4] にはインデックス 1, 2, 3 が含まれますが、インデックス 4 の手前で止まります。この「stop は含まない」というルールは、文字列スライスと同じです。
生徒名を使った実用例を見てみましょう。
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
# 最初の3人の生徒を取得
first_three = students[0:3]
print(first_three) # Output: ['Alice', 'Bob', 'Charlie']
# インデックス 2 から 4 までの生徒を取得
middle_group = students[2:5]
print(middle_group) # Output: ['Charlie', 'Diana', 'Eve']14.2.2) スライスで start または stop を省略する
start インデックスを省略すると先頭からスライスでき、stop インデックスを省略すると末尾までスライスできます。
scores = [85, 92, 78, 95, 88, 91, 87]
# 先頭からインデックス 3 の手前まで
first_few = scores[:3]
print(first_few) # Output: [85, 92, 78]
# インデックス 4 から末尾まで
last_few = scores[4:]
print(last_few) # Output: [88, 91, 87]
# リスト全体(先頭から末尾まで)
all_scores = scores[:]
print(all_scores) # Output: [85, 92, 78, 95, 88, 91, 87]スライス [:] はリスト全体の コピー(copy) を作成します。これは、元のリストを変更せずに複製したものを扱いたいときに便利です—この点は 14.6 節で詳しく見ていきます。
14.2.3) スライスで負のインデックスを使う
負のインデックスは、単一要素へのアクセスと同じようにスライスでも使えます。これは末尾から項目を取得するのに特に便利です。
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
# 最後の3人の生徒を取得
last_three = students[-3:]
print(last_three) # Output: ['Diana', 'Eve', 'Frank']
# 最後の2人を除いた全員を取得
all_but_last_two = students[:-2]
print(all_but_last_two) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']
# 末尾から3番目〜末尾から2番目の手前までを取得
middle_from_end = students[-3:-1]
print(middle_from_end) # Output: ['Diana', 'Eve']14.2.4) step 値を指定したスライス
3つ目のパラメータを追加して ステップ(step)(項目間で何個のインデックスを飛ばすか)を制御できます。完全な構文は list[start:stop:step] です。
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# インデックス 0 から2つおきの数
evens = numbers[0:10:2]
print(evens) # Output: [0, 2, 4, 6, 8]
# インデックス 1 から3つおきの数
every_third = numbers[1:10:3]
print(every_third) # Output: [1, 4, 7]また、負のステップ(negative step) を使うとリストを逆順にできます。
numbers = [1, 2, 3, 4, 5]
# リストを逆順にする
reversed_numbers = numbers[::-1]
print(reversed_numbers) # Output: [5, 4, 3, 2, 1]スライス [::-1] は「末尾から始め、先頭まで、1 ずつ逆向きに進む」という意味です。これはシーケンスを反転するための一般的な Python のイディオムです。
14.2.5) スライスは IndexError を起こさない
単一要素へのアクセスとは異なり、スライスはとても寛容です。リストの範囲外のインデックスを指定しても、Python がそれらを範囲に収まるように調整します。
numbers = [10, 20, 30, 40, 50]
# 存在する以上を要求する
extended_slice = numbers[2:100]
print(extended_slice) # Output: [30, 40, 50]
# 末尾を超えた位置から開始する
empty_slice = numbers[10:20]
print(empty_slice) # Output: []この挙動は便利で、スライスのときに厳密な境界を気にする必要がないことを意味します—Python が端のケースをうまく扱ってくれます。
14.3) リストの変更と代表的なリストメソッド
14.3.1) リストはミュータブル: 要素を変更する
イミュータブルな文字列とは違い、リストはミュータブル(mutable) です—作成後に内容を変更できます。特定のインデックスに新しい値を代入することで、個々の要素を変更できます。
# 価格のリストから開始
prices = [19.99, 24.50, 15.75, 32.00]
print(prices) # Output: [19.99, 24.5, 15.75, 32.0]
# 2番目の価格(インデックス 1)を更新
prices[1] = 22.99
print(prices) # Output: [19.99, 22.99, 15.75, 32.0]
# 負のインデックスを使って最後の価格を更新
prices[-1] = 29.99
print(prices) # Output: [19.99, 22.99, 15.75, 29.99]このミュータブル性は強力です。新しいリストを作らずに、その場でデータを更新できるからです。しかし意図しない変更にも注意する必要があり、これについては 14.6 節で説明します。
14.3.2) append() で要素を追加する
append() メソッドは、1つの項目をリストの 末尾(end) に追加します。これは最も頻繁に使われるリスト操作の1つです。
# 空のショッピングカートから開始
cart = []
print(cart) # Output: []
# 項目を1つずつ追加
cart.append("Milk")
print(cart) # Output: ['Milk']
cart.append("Bread")
print(cart) # Output: ['Milk', 'Bread']
cart.append("Eggs")
print(cart) # Output: ['Milk', 'Bread', 'Eggs']append() はリストを in place(その場)で変更し、新しいリストを返さないことに注目してください。メソッドは None を返すので、結果を代入する必要はありません。
scores = [85, 92, 78]
result = scores.append(95)
print(scores) # Output: [85, 92, 78, 95]
print(result) # Output: None14.3.3) insert() で特定の位置に要素を挿入する
append() は常に末尾に追加しますが、insert() を使うと任意の位置に項目を追加できます。構文は list.insert(index, item) です。
students = ["Alice", "Charlie", "Diana"]
print(students) # Output: ['Alice', 'Charlie', 'Diana']
# インデックス 1(Alice と Charlie の間)に "Bob" を挿入
students.insert(1, "Bob")
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']インデックスに挿入すると、その位置にあった項目(およびそれ以降の項目)は右へシフトします。
numbers = [10, 20, 30, 40]
print(numbers) # Output: [10, 20, 30, 40]
# インデックス 2 に 25 を挿入
numbers.insert(2, 25)
print(numbers) # Output: [10, 20, 25, 30, 40]インデックス 0 を使えば先頭に挿入できます。
priorities = ["Medium", "Low"]
priorities.insert(0, "High")
print(priorities) # Output: ['High', 'Medium', 'Low']リストの長さを超えるインデックスを指定しても、insert() は単に末尾に追加します(append() のように)。
items = [1, 2, 3]
items.insert(100, 4)
print(items) # Output: [1, 2, 3, 4]14.3.4) remove() で要素を削除する
remove() メソッドは、指定した値の 最初の出現(first occurrence) をリストから削除します。
fruits = ["apple", "banana", "cherry", "banana", "date"]
print(fruits) # Output: ['apple', 'banana', 'cherry', 'banana', 'date']
# 最初の "banana" を削除
fruits.remove("banana")
print(fruits) # Output: ['apple', 'cherry', 'banana', 'date']最初の "banana" だけが削除され、2つ目は残っていることに注目してください。存在しない値を削除しようとすると、Python は ValueError を発生させます。
numbers = [10, 20, 30]
# WARNING: 存在しない値を削除しようとしています - デモ用のみ
# PROBLEM: 40 はリスト内にありません
# numbers.remove(40) # ValueError: list.remove(x): x not in listこのエラーを避けるには、削除前に項目が存在するか確認できます。
cart = ["Milk", "Bread", "Eggs"]
item_to_remove = "Butter"
if item_to_remove in cart:
cart.remove(item_to_remove)
print(f"Removed {item_to_remove}")
else:
print(f"{item_to_remove} not in cart")
# Output: Butter not in cart14.3.5) pop() で要素を削除して返す
pop() メソッドは、特定のインデックスの項目を削除し、それを 返します(returns)。インデックスを指定しない場合、最後の項目を削除して返します。
scores = [85, 92, 78, 95, 88]
# 最後の点数を削除して取得
last_score = scores.pop()
print(f"Removed: {last_score}") # Output: Removed: 88
print(scores) # Output: [85, 92, 78, 95]
# インデックス 1 の点数を削除して取得
second_score = scores.pop(1)
print(f"Removed: {second_score}") # Output: Removed: 92
print(scores) # Output: [85, 78, 95]これは、リストから項目を1つずつ処理する必要があるときに便利です。
tasks = ["Write code", "Test code", "Deploy code"]
while len(tasks) > 0:
current_task = tasks.pop(0) # 先頭から削除
print(f"Working on: {current_task}")
# Output:
# Working on: Write code
# Working on: Test code
# Working on: Deploy code
print(tasks) # Output: []14.3.6) extend() でリストを拡張する
extend() メソッドは、別のリスト(または任意のイテラブル)からすべての項目を現在のリストの末尾に追加します。
primary_colors = ["red", "blue", "yellow"]
secondary_colors = ["green", "orange", "purple"]
# secondary_colors のすべてを primary_colors に追加
primary_colors.extend(secondary_colors)
print(primary_colors)
# Output: ['red', 'blue', 'yellow', 'green', 'orange', 'purple']これは append() と異なります。append() はリスト全体を1つの要素として追加します。
colors1 = ["red", "blue"]
colors2 = ["green", "orange"]
# append を使用(リストを1要素として追加)
colors1.append(colors2)
print(colors1) # Output: ['red', 'blue', ['green', 'orange']]
# extend を使用(各要素を個別に追加)
colors3 = ["red", "blue"]
colors3.extend(colors2)
print(colors3) # Output: ['red', 'blue', 'green', 'orange']14.3.7) sort() と sorted() でリストをソートする
Python にはリストをソートする2つの方法があります。sort() メソッドはリストを in place(元のリストを変更)でソートします。
scores = [78, 95, 85, 92, 88]
scores.sort()
print(scores) # Output: [78, 85, 88, 92, 95]降順でソートするには reverse パラメータを使います。
scores = [78, 95, 85, 92, 88]
scores.sort(reverse=True)
print(scores) # Output: [95, 92, 88, 85, 78]sorted() 関数(第38章でさらに詳しく扱います)は、元のリストを変更せずに 新しいソート済みリスト(new sorted list) を作成します。
original = [78, 95, 85, 92, 88]
sorted_scores = sorted(original)
print(original) # Output: [78, 95, 85, 92, 88]
print(sorted_scores) # Output: [78, 85, 88, 92, 95]文字列でも、アルファベット順でソートできます。
names = ["Charlie", "Alice", "Diana", "Bob"]
names.sort()
print(names) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']14.3.8) reverse() でリストを反転する
reverse() メソッドはリストを in place で反転します。
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(numbers) # Output: [5, 4, 3, 2, 1]これは逆順ソートとは異なります。reverse() は現在の並びを、そのまま単純にひっくり返すだけです。
mixed = [3, 1, 4, 1, 5]
mixed.reverse()
print(mixed) # Output: [5, 1, 4, 1, 3]スライス list[::-1] を使ってリストを反転できることも覚えておいてください。違いは、スライスは新しいリストを作成し、reverse() は元のリストを変更する点です。
14.3.9) index() と count() で要素を探す
index() メソッドは、ある値が最初に現れる位置を返します。
students = ["Alice", "Bob", "Charlie", "Diana", "Bob"]
# "Charlie" がどこにあるかを探す
position = students.index("Charlie")
print(f"Charlie is at index {position}") # Output: Charlie is at index 2
# 最初の "Bob" を探す
bob_position = students.index("Bob")
print(f"Bob is at index {bob_position}") # Output: Bob is at index 1値が存在しない場合、index() は ValueError を発生させます。
students = ["Alice", "Bob", "Charlie"]
# WARNING: 存在しない値を探そうとしています - デモ用のみ
# PROBLEM: 'Eve' はリスト内にありません
# position = students.index("Eve") # ValueError: 'Eve' is not in listcount() メソッドは、ある値が何回現れるかを返します。
numbers = [1, 2, 3, 2, 4, 2, 5]
twos = numbers.count(2)
print(f"The number 2 appears {twos} times") # Output: The number 2 appears 3 times
# 項目が存在しない場合は 0 を返します
sixes = numbers.count(6)
print(f"The number 6 appears {sixes} times") # Output: The number 6 appears 0 times14.3.10) clear() ですべての要素を削除する
clear() メソッドはリストからすべての項目を削除して空にします。
cart = ["Milk", "Bread", "Eggs", "Butter"]
print(cart) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
cart.clear()
print(cart) # Output: []
print(len(cart)) # Output: 0これは空のリストを代入するのと等価ですが、clear() のほうが意図(空にする)がより明確です。
14.4) del でリスト要素を削除する
14.4.1) del を使ってインデックスで要素を削除する
del 文は、特定のインデックスのリスト要素を削除できます。
students = ["Alice", "Bob", "Charlie", "Diana", "Eve"]
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']
# インデックス 2 の要素を削除
del students[2]
print(students) # Output: ['Alice', 'Bob', 'Diana', 'Eve']pop() と違い、del は削除した値を返しません。単に削除するだけです。項目を削除したいが、その値を使う必要がない場合に便利です。
scores = [85, 92, 78, 95, 88]
# 最も低い点数(インデックス 2)を削除
del scores[2]
print(scores) # Output: [85, 92, 95, 88]del は負のインデックスでも使えます。
tasks = ["Task 1", "Task 2", "Task 3", "Task 4"]
# 最後のタスクを削除
del tasks[-1]
print(tasks) # Output: ['Task 1', 'Task 2', 'Task 3']14.4.2) del でスライスを削除する
del 文はスライス全体を一度に削除できます。
numbers = [10, 20, 30, 40, 50, 60, 70]
print(numbers) # Output: [10, 20, 30, 40, 50, 60, 70]
# インデックス 2 から 4 まで(インデックス 2, 3, 4)を削除
del numbers[2:5]
print(numbers) # Output: [10, 20, 60, 70]これは範囲で要素を削除したいときに特に便利です。
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 最初の3要素を削除
del data[:3]
print(data) # Output: [4, 5, 6, 7, 8, 9, 10]
# 最後の2要素を削除
del data[-2:]
print(data) # Output: [4, 5, 6, 7, 8]ステップ付きスライスを使って、1つおきの要素を削除することもできます。
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 2つおきの要素を削除
del numbers[::2]
print(numbers) # Output: [1, 3, 5, 7, 9]14.4.3) del、remove()、pop() の比較
それぞれの削除方法をいつ使うべきか、整理しておきましょう。
# 比較のための例のリスト
items = ["apple", "banana", "cherry", "date", "elderberry"]
# 削除したい VALUE が分かっているときは remove() を使う
items_copy1 = items.copy()
items_copy1.remove("cherry") # 最初の "cherry" を削除
print(items_copy1) # Output: ['apple', 'banana', 'date', 'elderberry']
# INDEX が分かっていて、その値も必要なときは pop() を使う
items_copy2 = items.copy()
removed_item = items_copy2.pop(2) # インデックス 2 の項目を削除して返す
print(f"Removed: {removed_item}") # Output: Removed: cherry
print(items_copy2) # Output: ['apple', 'banana', 'date', 'elderberry']
# INDEX が分かっているが値は不要なときは del を使う
items_copy3 = items.copy()
del items_copy3[2] # インデックス 2 の項目を削除するだけ
print(items_copy3) # Output: ['apple', 'banana', 'date', 'elderberry']14.5) for ループ(loop)でリストを反復処理する
14.5.1) 基本的なリストの反復処理
リストで最もよく行う操作の1つは、各項目を順番に処理することです。第12章で学んだ for ループは、これに最適です。
students = ["Alice", "Bob", "Charlie", "Diana"]
# 各生徒を処理
for student in students:
print(f"Hello, {student}!")
# Output:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!
# Hello, Diana!ループ変数(この場合は student)は、リストの各値を順番に1つずつ受け取ります。この変数名は、意味のある任意の名前にできます。
scores = [85, 92, 78, 95, 88]
# 各点数の成績を計算して表示
for score in scores:
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
print(f"Score {score} is a {grade}")
# Output:
# Score 85 is a B
# Score 92 is a A
# Score 78 is a C
# Score 95 is a A
# Score 88 is a B14.5.2) 複数リストから対応する項目を処理する
関連データが別々のリストに保存されている場合、それらを一緒に扱う必要があることがあります。zip() 関数については第38章で詳しく学びますが、対応する項目を処理するのに役立つ方法を簡単に紹介します。
# zip() は第38章で学びますが、ここでは簡単な例を示します
students = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
# 対応するペアを処理
for student, score in zip(students, scores):
print(f"{student} scored {score}")
# Output:
# Alice scored 85
# Bob scored 92
# Charlie scored 78zip() 関数は複数のリストの要素をペアにします。関連データが別々のリストにあるときに便利です。これやその他の反復処理ツールは、第38章で深く掘り下げます。
14.6) リストのコピーと共有参照を避ける方法
14.6.1) リスト参照を理解する
リストを変数に代入しても、Python はリストのコピーを作るのではなく、メモリ上の同じリストオブジェクトへの 参照(reference) を作ります。つまり複数の変数が同じリストを参照できます。
original = [1, 2, 3]
reference = original # 両方の変数が同じリストを指します
# 片方の変数から変更すると、もう片方にも影響します
reference.append(4)
print(original) # Output: [1, 2, 3, 4]
print(reference) # Output: [1, 2, 3, 4]reference が独立したコピーだと期待していると、この挙動は驚きかもしれません。なぜ重要か見てみましょう。
# シナリオ: ショッピングカートの変更を追跡したい
cart = ["Milk", "Bread"]
backup = cart # 元の状態を保存しようとしている
# さらに項目を追加
cart.append("Eggs")
cart.append("Butter")
# "backup" を確認
print(backup) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']バックアップまで変わってしまいました! これは backup と cart が同じリストオブジェクトの別名だからです。
14.6.2) スライスで独立したコピーを作る
本当に独立したコピーを作るには、[:] を使ったスライスを使います。
original = [1, 2, 3]
copy = original[:] # 同じ内容を持つ新しいリストを作成
# copy を変更しても original には影響しません
copy.append(4)
print(original) # Output: [1, 2, 3]
print(copy) # Output: [1, 2, 3, 4]ではショッピングカートの例を修正してみましょう。
cart = ["Milk", "Bread"]
backup = cart[:] # 独立したコピーを作成
# cart に項目を追加
cart.append("Eggs")
cart.append("Butter")
# backup は変わりません
print(cart) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
print(backup) # Output: ['Milk', 'Bread']14.6.3) copy() メソッドでコピーを作る
リストには copy() メソッドもあり、[:] と同じことをします。
original = [10, 20, 30]
copy = original.copy()
copy.append(40)
print(original) # Output: [10, 20, 30]
print(copy) # Output: [10, 20, 30, 40][:] も copy() も 浅いコピー(shallow copies) を作成します。これについて次に説明します。
14.6.4) 浅いコピーの制限
[:] と copy() はどちらも 浅いコピー(shallow copies) を作成します。これはリスト構造自体はコピーしますが、リストが他のミュータブルなオブジェクト(他のリストなど)を含む場合、それら内側のオブジェクトは共有されたままであることを意味します。
# リストを含むリスト
original = [[1, 2], [3, 4], [5, 6]]
copy = original[:]
# 外側のリスト構造の変更は独立しています
copy.append([7, 8])
print(original) # Output: [[1, 2], [3, 4], [5, 6]]
print(copy) # Output: [[1, 2], [3, 4], [5, 6], [7, 8]]
# しかし内側のリストを変更すると両方に影響します!
copy[0].append(99)
print(original) # Output: [[1, 2, 99], [3, 4], [5, 6]]
print(copy) # Output: [[1, 2, 99], [3, 4], [5, 6], [7, 8]]なぜこうなるのでしょうか。浅いコピーは新しい外側のリストを作成しますが、内側のリストは参照が共有されたままだからです。
入れ子構造では 深いコピー(deep copy) が必要になりますが、これは後の章で copy モジュールを扱うときに学びます。今は、浅いコピーは(数値、文字列、タプルのような)イミュータブルな項目のリストでは問題なく機能しますが、入れ子のミュータブル構造では注意が必要だと覚えておいてください。
14.6.5) 共有参照が役立つ場面
場合によっては、複数の変数が同じリストを参照することを 望む こともあります。これは、コードの異なる場所から同じリストを変更する必要があるときに便利です。
# リストを in place で変更する関数
def add_bonus_points(scores, bonus):
for i in range(len(scores)):
scores[i] = scores[i] + bonus
# 元のリストが変更されます
student_scores = [85, 92, 78]
add_bonus_points(student_scores, 5)
print(student_scores) # Output: [90, 97, 83]これは関数がコピーではなく元のリストへの参照を受け取るために成り立っています。これについては、Part V で関数を詳しく学ぶときにさらに掘り下げます。
14.7) リストのループ中に enumerate() を使う
14.7.1) インデックスと値の両方が必要になる場面
リストを反復処理するとき、インデックスと値の両方が必要になることがあります。1つの方法は range(len(list)) を使うことです。
students = ["Alice", "Bob", "Charlie", "Diana"]
for i in range(len(students)):
print(f"Student {i}: {students[i]}")
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: Dianaこれは動作しますが、あまり洗練されていません。各値にアクセスするのに students[i] を使う必要があり、値を直接反復するより読みやすさが落ちます。
14.7.2) enumerate() でより読みやすいコードにする
enumerate() 関数はより良い解決策を提供します。各項目について、インデックスと値の両方を返します。
students = ["Alice", "Bob", "Charlie", "Diana"]
for index, student in enumerate(students):
print(f"Student {index}: {student}")
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: Diana構文 for index, value in enumerate(list) は、enumerate() が生成する各ペアをアンパックしています。これは range(len()) を使うよりずっと読みやすいです。
14.7.3) enumerate() を別の数から始める
デフォルトでは enumerate() は 0 から数えます。start パラメータを使えば、別の開始番号を指定できます。
students = ["Alice", "Bob", "Charlie", "Diana"]
# 0 ではなく 1 から数える
for position, student in enumerate(students, start=1):
print(f"Position {position}: {student}")
# Output:
# Position 1: Alice
# Position 2: Bob
# Position 3: Charlie
# Position 4: Dianaこれは、プログラマ向けのインデックス(0始まり)ではなく、人間にとって自然な番号(1始まり)で表示したいときに便利です。
enumerate() を使った実用例
番号付きメニューを表示する実用例です。
menu_items = ["New Game", "Load Game", "Settings", "Quit"]
print("Main Menu:")
for number, item in enumerate(menu_items, start=1):
print(f"{number}. {item}")
# Output:
# Main Menu:
# 1. New Game
# 2. Load Game
# 3. Settings
# 4. Quit14.7.4) enumerate() でリストを変更する
位置に基づいてリスト要素を変更する必要がある場合、enumerate() を使えます。
# 位置に応じたボーナスを点数に加算
scores = [85, 92, 78, 95, 88]
for index, score in enumerate(scores):
# 1人目は 5 点のボーナス、2人目は 4 点、というように
bonus = 5 - index
if bonus > 0:
scores[index] = score + bonus
print(scores) # Output: [90, 96, 81, 97, 89]14.8) よくあるリストパターン: 検索、フィルタリング、集計
14.8.1) リスト内の項目を検索する
最もよくあるタスクの1つは、リストに特定の項目が含まれているかを確認することです。第7章で学んだ in 演算子で簡単にできます。
students = ["Alice", "Bob", "Charlie", "Diana"]
# 生徒がリストにいるか確認
if "Charlie" in students:
print("Charlie is enrolled") # Output: Charlie is enrolled
if "Eve" not in students:
print("Eve is not enrolled") # Output: Eve is not enrolled項目の位置を探すには index() メソッド(14.3.9 節で扱いました)を使いますが、その前に項目が存在するか確認することを忘れないでください。
scores = [85, 92, 78, 95, 88]
target_score = 95
if target_score in scores:
position = scores.index(target_score)
print(f"Score {target_score} found at index {position}")
# Output: Score 95 found at index 3
else:
print(f"Score {target_score} not found")14.8.2) 最大値と最小値を見つける
Python の組み込み関数 max() と min() はリストでも使えます。
scores = [85, 92, 78, 95, 88, 91, 76]
highest_score = max(scores)
lowest_score = min(scores)
print(f"Highest score: {highest_score}") # Output: Highest score: 95
print(f"Lowest score: {lowest_score}") # Output: Lowest score: 7614.8.3) 集計を計算する: 合計、平均、個数
合計や平均の計算は、基本的なリスト操作です。
scores = [85, 92, 78, 95, 88, 91, 76, 89]
# 合計と平均を計算
total = sum(scores)
count = len(scores)
average = total / count
print(f"Total: {total}") # Output: Total: 694
print(f"Count: {count}") # Output: Count: 8
print(f"Average: {average:.2f}") # Output: Average: 86.75ショッピングカートの合計を計算する実用例です。
cart_items = ["Milk", "Bread", "Eggs", "Butter", "Cheese"]
prices = [3.99, 2.49, 4.99, 5.49, 6.99]
# 合計コストを計算
total_cost = sum(prices)
item_count = len(cart_items)
print(f"Items in cart: {item_count}")
print(f"Total cost: ${total_cost:.2f}")
# Output:
# Items in cart: 5
# Total cost: $23.9514.9) リストのミュータブル性と条件での真偽値評価
14.9.1) 実例でリストのミュータブル性を理解する
この章を通して、リストが ミュータブル(mutable) であり、作成後に変更できることを見てきました。このミュータブル性が、リストをデータコレクションの保存と操作において強力な存在にしています。理解をまとめるために、包括的な例を見てみましょう。
# 空のタスクリストから開始
tasks = []
print(f"Initial tasks: {tasks}") # Output: Initial tasks: []
# タスクを追加
tasks.append("Write code")
tasks.append("Test code")
tasks.append("Deploy code")
print(f"After adding: {tasks}")
# Output: After adding: ['Write code', 'Test code', 'Deploy code']
# 緊急タスクを先頭に挿入
tasks.insert(0, "Review requirements")
print(f"After inserting: {tasks}")
# Output: After inserting: ['Review requirements', 'Write code', 'Test code', 'Deploy code']
# 最初のタスクを完了して削除
completed = tasks.pop(0)
print(f"Completed: {completed}") # Output: Completed: Review requirements
print(f"Remaining: {tasks}")
# Output: Remaining: ['Write code', 'Test code', 'Deploy code']
# タスクを変更
tasks[1] = "Test code thoroughly"
print(f"After modifying: {tasks}")
# Output: After modifying: ['Write code', 'Test code thoroughly', 'Deploy code']14.9.2) ミュータブル vs イミュータブル: リストと文字列
ミュータブルなリストとイミュータブルな文字列の違いを理解することは重要です。文字列では、操作は元の文字列を変更するのではなく新しい文字列を作成します。
# 文字列はイミュータブルです
text = "hello"
text.upper() # 新しい文字列を作成し、元は変わりません
print(text) # Output: hello (unchanged)
# 文字列を「変更」するには再代入が必要です
text = text.upper()
print(text) # Output: HELLO
# リストはミュータブルです
numbers = [1, 2, 3]
numbers.append(4) # リストを in place で変更
print(numbers) # Output: [1, 2, 3, 4] (changed)この違いは、これらの型の扱い方に影響します。
# 文字列の操作には再代入が必要
name = "alice"
name = name.capitalize() # 変化を反映するには再代入が必要
print(name) # Output: Alice
# リスト操作は in place で変更
scores = [85, 92, 78]
scores.append(95) # 再代入は不要
print(scores) # Output: [85, 92, 78, 95]14.9.3) 真偽値コンテキストでリストを使う
リストには truthiness があります。空のリストは False とみなされ、空でないリストは True とみなされます。これは条件文で便利です。
# 空のリストは falsy
empty_cart = []
if empty_cart:
print("Cart has items")
else:
print("Cart is empty") # Output: Cart is empty
# 空でないリストは truthy
cart_with_items = ["Milk", "Bread"]
if cart_with_items:
print("Cart has items") # Output: Cart has itemsこのパターンは、処理する前にリストに要素があるかを確認するのによく使われます。
students = ["Alice", "Bob", "Charlie"]
if students:
print(f"We have {len(students)} students")
for student in students:
print(f" - {student}")
else:
print("No students enrolled")
# Output:
# We have 3 students
# - Alice
# - Bob
# - Charlie14.9.4) 実用パターン: 空になるまで処理する
リストの truthiness によって、リストが空になるまで項目を処理する便利なパターンが可能になります。
# タスクがなくなるまで処理
tasks = ["Task 1", "Task 2", "Task 3"]
while tasks: # リストが空でない間は継続
current_task = tasks.pop(0)
print(f"Processing: {current_task}")
print("All tasks completed!")
# Output:
# Processing: Task 1
# Processing: Task 2
# Processing: Task 3
# All tasks completed!14.9.5) 空リストのチェック: 明示的 vs 暗黙的
リストが空かどうかを確認する方法は2つあります。
items = []
# 暗黙的チェック(Pythonic)
if not items:
print("List is empty") # Output: List is empty
# 明示的チェック(これも有効)
if len(items) == 0:
print("List is empty") # Output: List is empty暗黙的チェック(if not items:)は Python では一般的に好まれます。より簡潔で、あらゆるコレクション型に対して機能するからです。ただし両方の方法は正しく、実際のコードではどちらも見かけます。
14.9.6) ミュータブル性と関数の振る舞い
リストを関数に渡すと(Part V で詳しく扱います)、関数は同じリストオブジェクトへの参照を受け取ります。つまり関数が元のリストを変更できます。
def add_item(shopping_list, item):
shopping_list.append(item)
print(f"Added {item}")
# 元のリストが変更されます
cart = ["Milk", "Bread"]
print(f"Before: {cart}") # Output: Before: ['Milk', 'Bread']
add_item(cart, "Eggs") # Output: Added Eggs
print(f"After: {cart}") # Output: After: ['Milk', 'Bread', 'Eggs']この挙動は、文字列や数値のようなイミュータブル型とは異なります。イミュータブル型では、関数によって元の値を変更できません。この違いを理解することは、正しいプログラムを書くうえで非常に重要です。
リストは Python の最も基本的で汎用的なデータ構造の1つです。リストは順序付きでミュータブルなコレクションを提供し、必要に応じて伸縮できるため、関連データの並びを保存して処理するのに最適です。この章では、リストの作成、インデックス指定やスライスによる要素へのアクセス、各種メソッドによる変更、効率的な反復処理、そしてミュータブルな性質の理解を学びました。
今回扱ったパターン—検索、フィルタリング、集計、変換—は、Python でコレクションを扱うための土台になります。学習を続けると、リスト内包表記(第35章)や高度な反復処理テクニック(第36〜37章)など、リストをさらに強力に使う方法が見つかるでしょう。しかし、この章で身につけた基礎は、Python プログラミングの学習を通して大いに役立つはずです。