Python初心者必見!リストの基本をゼロから徹底解説

Python初心者必見!リストの基本をゼロから徹底解説

Pythonの学習を始めた皆さん、こんにちは!プログラミングの基本を学ぶ上で、避けて通れないのが「データ構造」です。データ構造は、プログラムの中でデータをどのように整理し、格納するかを決めるものです。そして、Pythonで最も基本的で、最もよく使われるデータ構造の一つが「リスト(list)」です。

リストをマスターすることは、Pythonを使ったプログラミングにおいて非常に強力な武器となります。なぜなら、リストは様々な種類のデータをまとめて扱え、必要に応じて中身を変更できる、非常に柔軟な構造だからです。

この記事では、Python初心者の方を対象に、リストの基本中の基本から、少し応用的な使い方まで、約5000語のボリュームで徹底的に解説します。この記事を読み終える頃には、リストがどんなもので、どのように作成し、操作できるのかをしっかりと理解できるようになるはずです。

さあ、Pythonリストの世界への冒険を始めましょう!

1. はじめに:なぜリストが重要なのか?

私たちの身の回りには、複数のものが集まって一つのまとまりをなしている例がたくさんあります。例えば、買い物リスト、TODOリスト、クラス名簿、試験の点数一覧などです。プログラムの世界でも、このような「複数のデータ」をまとめて扱いたい場面は頻繁にあります。

Pythonには、複数のデータを扱うためのデータ構造がいくつか用意されています。代表的なものとしては、リスト(list)、タプル(tuple)、セット(set)、辞書(dict)などがあります。これらはそれぞれ異なる特徴を持ち、用途に応じて使い分けられます。

その中でも、リストは最も汎用的で、最もよく使われるデータ構造です。その理由は以下の通りです。

  • 複数の要素を順番に格納できる: 要素にはインデックス(添え字)が付いており、順番が保証されます。
  • 様々な型のデータを格納できる: 数値、文字列、他のリスト、さらには独自に作成したオブジェクトなど、どんな型のデータでも混ぜて格納できます。
  • 中身を変更できる(ミュータブル): 一度作ったリストに要素を追加したり、削除したり、変更したりすることが簡単にできます。

これらの特徴から、リストはデータの集まりを柔軟に扱いたいあらゆる場面で活躍します。この記事では、このリストの作成方法から、要素へのアクセス、変更、追加、削除、そして便利な操作方法まで、具体的なコード例を交えながら詳しく見ていきます。

2. リストとは?その定義と基本的な特徴

改めて、リストとは「複数の要素を順序付けて格納できる、変更可能な(ミュータブルな)データ構造」です。

Pythonでは、リストは [] (角括弧) を使って表現します。要素はカンマ , で区切ります。

例えば、以下のようなものがリストです。

“`python

空のリスト

empty_list = []

文字列のリスト

fruits = [“apple”, “banana”, “cherry”]

数値のリスト

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

異なる型の要素が混在するリスト

mixed_list = [“hello”, 123, 3.14, True, None]

リストの中にリストを持つ(ネストしたリスト)

nested_list = [[1, 2], [3, 4], [5, 6]]
“`

リストの基本的な特徴をまとめると以下のようになります。

  • 順序がある: リストに格納した要素は、追加した順番が保たれます。各要素には0から始まるインデックスが割り当てられます。
  • インデックスでアクセスできる: 順序があるため、0番目の要素、1番目の要素…といった形で、インデックスを使って個々の要素にアクセスできます。
  • 変更可能(ミュータブル): リストを作成した後でも、要素の値を変更したり、要素を追加・削除したりすることができます。これがタプルなどの変更不可能なデータ構造との大きな違いです。
  • 重複する要素を持てる: 同じ値を持つ要素を複数格納できます。
  • 様々な型の要素を格納できる: 整数、浮動小数点数、文字列、真偽値、None、他のリスト、タプル、辞書など、どんなオブジェクトでもリストの要素として格納できます。

これらの特徴を理解しておくことが、リストを効果的に使うための第一歩です。

3. リストの作成

リストを作成する方法はいくつかあります。ここでは、最も一般的な方法から紹介します。

3.1. 空のリストを作成する

要素が何も入っていない空のリストは、 [] または list() を使って作成できます。

“`python

方法1: [] を使う

my_list1 = []
print(my_list1) # 出力: []
print(type(my_list1)) # 出力:

方法2: list() 関数を使う

my_list2 = list()
print(my_list2) # 出力: []
print(type(my_list2)) # 出力:
“`

どちらの方法でも同じ空のリストが作成されます。一般的には [] の方が簡潔なのでよく使われますが、list() 関数は後述する「他のイテラブルからリストを作成する」際にも使われます。

3.2. 要素を持つリストを作成する

最も基本的な方法は、要素を角括弧 [] の中にカンマ区切りで直接記述する方法です。

“`python

数値のリスト

numbers = [10, 20, 30, 40, 50]
print(numbers) # 出力: [10, 20, 30, 40, 50]

文字列のリスト

colors = [“red”, “green”, “blue”]
print(colors) # 出力: [‘red’, ‘green’, ‘blue’]

異なる型の要素が混在するリスト

info = [“Alice”, 25, 1.65, True]
print(info) # 出力: [‘Alice’, 25, 1.65, True]

ネストしたリスト

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matrix) # 出力: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
“`

このように、どんな型のデータでも要素として含めることができます。

3.3. 他のイテラブルからリストを作成する

文字列やタプルなど、繰り返し処理が可能なデータ構造(イテラブル)から、list() 関数を使って新しいリストを作成することもできます。

“`python

文字列からリストを作成 (各文字が要素になる)

my_string = “Python”
string_list = list(my_string)
print(string_list) # 出力: [‘P’, ‘y’, ‘t’, ‘h’, ‘o’, ‘n’]

タプルからリストを作成

my_tuple = (1, 2, 3, 4, 5)
tuple_list = list(my_tuple)
print(tuple_list) # 出力: [1, 2, 3, 4, 5]

セットからリストを作成 (セットは順序がないため、リストの順序は保証されない)

my_set = {10, 20, 30}
set_list = list(my_set)
print(set_list) # 出力例: [10, 20, 30] (実行ごとに順序が変わる可能性あり)

辞書のキーからリストを作成

my_dict = {“apple”: 100, “banana”: 200, “cherry”: 300}
dict_keys_list = list(my_dict)
print(dict_keys_list) # 出力例: [‘apple’, ‘banana’, ‘cherry’] (Python 3.7+ では挿入順が保持される)

辞書の値からリストを作成

dict_values_list = list(my_dict.values())
print(dict_values_list) # 出力例: [100, 200, 300]

辞書のキーと値のペア(タプル形式)からリストを作成

dict_items_list = list(my_dict.items())
print(dict_items_list) # 出力例: [(‘apple’, 100), (‘banana’, 200), (‘cherry’, 300)]
“`

list() 関数は、引数として与えられたイテラブルの各要素を取り出し、それらを要素とする新しいリストを作成します。

3.4. リスト内包表記を使った作成 (導入)

リスト内包表記(List Comprehension)は、既存のリストや他のイテラブルから、新しいリストを簡潔かつ効率的に作成するためのPython特有の構文です。これは非常に強力な機能ですが、最初は少し難しく感じるかもしれません。ここでは簡単な例だけを紹介し、詳細は後ほど改めて解説します。

“`python

1から10までの数字のリストを作成

通常のループの場合

numbers = []
for i in range(1, 11):
numbers.append(i)
print(numbers) # 出力: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

リスト内包表記の場合

numbers_comp = [i for i in range(1, 11)]
print(numbers_comp) # 出力: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

偶数の2乗のリストを作成

通常のループの場合

even_squares = []
for i in range(1, 11):
if i % 2 == 0:
even_squares.append(i**2)
print(even_squares) # 出力: [4, 16, 36, 64, 100]

リスト内包表記の場合

even_squares_comp = [i**2 for i in range(1, 11) if i % 2 == 0]
print(even_squares_comp) # 出力: [4, 16, 36, 64, 100]
“`

リスト内包表記を使うと、このように数行のコードを1行に短くまとめることができます。最初はループを使った方法で理解を進め、慣れてきたらリスト内包表記を使ってみるのが良いでしょう。

4. リストの要素へのアクセス

リストの要素には、その要素がリストの中で何番目にあるかを示す「インデックス(index)」を使ってアクセスします。Pythonのインデックスは0から始まることに注意してください。

“`python
fruits = [“apple”, “banana”, “cherry”, “date”, “elderberry”]

最初の要素 (インデックス0)

print(fruits[0]) # 出力: apple

2番目の要素 (インデックス1)

print(fruits[1]) # 出力: banana

3番目の要素 (インデックス2)

print(fruits[2]) # 出力: cherry

最後の要素 (インデックス4)

print(fruits[4]) # 出力: elderberry
“`

4.1. 負のインデックスを使ったアクセス

Pythonでは、リストの末尾から要素にアクセスするために、負のインデックスを使うこともできます。

  • -1 は最後の要素
  • -2 は最後から2番目の要素
  • …といった具合です。

“`python
fruits = [“apple”, “banana”, “cherry”, “date”, “elderberry”]

最後の要素 (インデックス -1)

print(fruits[-1]) # 出力: elderberry

最後から2番目の要素 (インデックス -2)

print(fruits[-2]) # 出力: date

最初の要素 (インデックス -5)

print(fruits[-5]) # 出力: apple
“`

負のインデックスを使うと、リストの長さを知らなくても最後の要素や末尾近くの要素に簡単にアクセスできます。

4.2. インデックスエラー (IndexError)

存在しないインデックスを指定して要素にアクセスしようとすると、IndexError が発生します。

“`python
fruits = [“apple”, “banana”, “cherry”]

リストの長さは 3 なので、インデックスは 0, 1, 2 です。

存在しないインデックス 3 を指定してみます。

print(fruits[3]) # -> IndexError: list index out of range

存在しない負のインデックス -4 を指定してみます。

print(fruits[-4]) # -> IndexError: list index out of range

“`

プログラムを書く際には、リストの範囲を超えるインデックスを指定しないように注意が必要です。リストの長さを知りたい場合は、組み込み関数 len() を使います。

“`python
fruits = [“apple”, “banana”, “cherry”]
print(len(fruits)) # 出力: 3

最後の要素のインデックスは len(fruits) – 1 です

print(fruits[len(fruits) – 1]) # 出力: cherry

これは fruits[-1] と同じ意味になります

“`

4.3. ネストしたリストへのアクセス

リストの中にリストがある(ネストしたリスト)場合、要素にアクセスするにはインデックスを連続して使います。

“`python
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]

1行目のリストにアクセス

print(matrix[0]) # 出力: [1, 2, 3]

2行目のリストにアクセス

print(matrix[1]) # 出力: [4, 5, 6]

1行目のリストの2番目の要素 (値 2) にアクセス

print(matrix[0][1]) # 出力: 2

3行目のリストの3番目の要素 (値 9) にアクセス

print(matrix[2][2]) # 出力: 9
“`

これは、matrix[0] でまず [1, 2, 3] というリストを取り出し、そのリストに対して改めてインデックス [1] を適用している、と考えられます。

5. リストのスライス

リストから単一の要素を取り出すのがインデックスでしたが、複数の要素をまとめて「切り出す」にはスライスを使います。スライスは [開始インデックス:終了インデックス] の形式で指定します。

“`python
numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

インデックス2からインデックス5まで (終了インデックス5は含まない)

print(numbers[2:5]) # 出力: [30, 40, 50]

最初からインデックス3まで (終了インデックス3は含まない)

print(numbers[:3]) # 出力: [10, 20, 30]

インデックス7から最後まで

print(numbers[7:]) # 出力: [80, 90, 100]

全ての要素 (リスト全体のコピーを作成する際によく使われます)

print(numbers[:]) # 出力: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
“`

5.1. スライスのルール:終了インデックスは含まれない

スライスの [開始インデックス:終了インデックス] という記法では、開始インデックスの要素は含まれますが、終了インデックスの要素は含まれないという重要なルールがあります。

例えば、numbers[2:5] は、インデックス2、インデックス3、インデックス4の要素を含みますが、インデックス5の要素は含まれません。

これは、リストの長さを len(list) としたときに、list[0:len(list)] がリスト全体を表すなど、様々な操作で直感的に扱えるようにするための設計思想です。

5.2. ステップを指定したスライス

スライスには、さらに [開始インデックス:終了インデックス:ステップ] の形式でステップ(飛び幅)を指定することもできます。

“`python
numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

最初から最後まで、2つおきに要素を取得

print(numbers[::2]) # 出力: [10, 30, 50, 70, 90]

インデックス1からインデックス8まで (90は含まない)、3つおきに要素を取得

print(numbers[1:8:3]) # 出力: [20, 50, 80]

スライスの開始・終了・ステップを省略することも可能

例: 最初から最後まで、デフォルトステップ(1) -> [:] と同じ

print(numbers[::]) # 出力: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
“`

5.3. 負のステップを使ったスライス

ステップに負の値を指定すると、リストを逆順に辿ることができます。

“`python
numbers = [10, 20, 30, 40, 50]

リスト全体を逆順にする (ステップ -1 を指定)

print(numbers[::-1]) # 出力: [50, 40, 30, 20, 10]

インデックス -1 (50) からインデックス -4 (20) までを、逆順に辿って取得

(終了インデックス -4 は含まれないため、要素 20 は含まれない)

実際にはインデックス -1, -2, -3 の要素が取得されます

print(numbers[-1:-4:-1]) # 出力: [50, 40, 30]

インデックス -2 (40) から最初までを、逆順に辿って取得 (開始インデックスは省略可能)

print(numbers[-2::-1]) # 出力: [40, 30, 20, 10]

最後からインデックス 2 (30) までを、逆順に辿って取得 (終了インデックスは省略可能)

print(numbers[:-3:-1]) # 出力: [50, 40]
“`

負のステップを使う際のスライスの挙動(開始と終了の解釈)は少しややこしいですが、「開始インデックスから開始し、ステップの方向に進んでいき、終了インデックスの手前で止まる」という基本ルールは変わりません。逆順の場合は、大きいインデックスから小さいインデックスに向かって進みます。

5.4. スライスの結果は新しいリスト

スライス操作の結果は、元のリストの一部を参照するのではなく、新しいリストとして作成されます

“`python
original_list = [1, 2, 3, 4, 5]
sliced_list = original_list[1:4]

print(original_list) # 出力: [1, 2, 3, 4, 5]
print(sliced_list) # 出力: [2, 3, 4]

元のリストを変更しても、スライスしたリストは影響を受けません

original_list[2] = 99
print(original_list) # 出力: [1, 2, 99, 4, 5]
print(sliced_list) # 出力: [2, 3, 4] (変化なし)
“`

この性質は、リストのコピーを作成したり、リストの一部を安全に処理したりする際に重要になります。

6. リストの変更(ミュータブルな性質)

リストが「ミュータブル(変更可能)」であるということは、作成後にその中身を自由に変更できるということです。ここでは、要素の値を変更したり、複数の要素をまとめて変更したりする方法を見ていきます。

6.1. 要素の代入による変更

特定のインデックスにある要素の値を変更するには、インデックスを使ってその要素に新しい値を代入します。

“`python
fruits = [“apple”, “banana”, “cherry”]
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’]

インデックス1の要素を “grape” に変更

fruits[1] = “grape”
print(fruits) # 出力: [‘apple’, ‘grape’, ‘cherry’]

最後の要素を “orange” に変更 (負のインデックスを使用)

fruits[-1] = “orange”
print(fruits) # 出力: [‘apple’, ‘grape’, ‘orange’]
“`

存在しないインデックスに代入しようとすると、要素へのアクセスと同様に IndexError が発生します。

6.2. スライスを使った複数要素の変更/置換

スライスを使って指定した範囲の要素を、別のリストやイテラブルの要素で置き換えることができます。このとき、置き換える要素の数と元のスライスの長さは一致している必要はありません

“`python
numbers = [10, 20, 30, 40, 50]
print(numbers) # 出力: [10, 20, 30, 40, 50]

インデックス1から3まで ([20, 30, 40]) を、[99, 88, 77] で置き換え

numbers[1:4] = [99, 88, 77]
print(numbers) # 出力: [10, 99, 88, 77, 50] # リストの長さは変わらない

インデックス1から3まで ([99, 88, 77]) を、[100, 200] で置き換え (置き換える要素が少ない場合)

numbers[1:4] = [100, 200]
print(numbers) # 出力: [10, 100, 200, 50] # リストの長さが短くなる

インデックス1から3まで ([100, 200, 50]) を、[1, 2, 3, 4] で置き換え (置き換える要素が多い場合)

numbers[1:4] = [1, 2, 3, 4]
print(numbers) # 出力: [10, 1, 2, 3, 4] # リストの長さが長くなる

空のスライスに要素を代入すると、その位置に要素を挿入できます

numbers = [10, 20, 30, 40, 50]
numbers[2:2] = [999, 888] # インデックス2の位置に挿入
print(numbers) # 出力: [10, 20, 999, 888, 30, 40, 50]

最初の位置に挿入

numbers[:0] = [-1, -2]
print(numbers) # 出力: [-1, -2, 10, 20, 999, 888, 30, 40, 50]

最後の位置に挿入 (append() と似た効果)

numbers[len(numbers):] = [1000]
print(numbers) # 出力: [-1, -2, 10, 20, 999, 888, 30, 40, 50, 1000]
“`

6.3. スライスを使った要素の削除 (代入による)

スライスを使って指定した範囲の要素を削除するには、そのスライスに空のリスト [] を代入します。

“`python
numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
print(numbers) # 出力: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

インデックス2から5まで ([30, 40, 50]) を削除

numbers[2:6] = []
print(numbers) # 出力: [10, 20, 60, 70, 80, 90, 100] # [30, 40, 50, 60] が削除された

ステップを指定したスライスに空リストを代入すると、飛び飛びの要素を削除できます

numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
numbers[::2] = [] # 偶数インデックスの要素を削除しようとする

print(numbers) # -> ValueError: attempt to assign sequence of size 0 to extended slice of size 5

“`

注意点: ステップを指定したスライスに代入する場合、代入するリストの要素数と、スライスで指定された範囲の要素数は厳密に一致している必要があります。空リスト [] は要素数が0なので、ステップを指定したスライスの場合は基本的に代入による削除はできません。(もし代入したい場合は、スライスで取得される要素数と同じ数の要素を持つリストを代入する必要があります。)

ステップを指定せずに numbers[2:6] = [] のように使うことで、連続した範囲の要素を効率的に削除できます。

7. リストへの要素の追加

リストに新しい要素を追加する方法はいくつかあります。用途に応じて使い分けましょう。

7.1. append() メソッド

リストの末尾に単一の要素を追加するには、append() メソッドを使います。

“`python
fruits = [“apple”, “banana”]
print(fruits) # 出力: [‘apple’, ‘banana’]

fruits.append(“cherry”)
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’]

append() の引数がリストの場合、そのリスト全体が1つの要素として追加されます

fruits.append([“date”, “elderberry”])
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’, [‘date’, ‘elderberry’]]
“`

append() はリスト自体を変更します(インプレース操作)。

7.2. extend() メソッド

リストの末尾に、別のイテラブル(リスト、タプル、文字列など)の要素を一つずつ追加するには、extend() メソッドを使います。これは、2つのリストを連結するようなイメージです。

“`python
fruits = [“apple”, “banana”]
print(fruits) # 出力: [‘apple’, ‘banana’]

more_fruits = [“cherry”, “date”]
fruits.extend(more_fruits)
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’, ‘date’] # 各要素が追加された

extend() の引数が文字列の場合、各文字が要素として追加されます

fruits.extend(“grape”)
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’, ‘date’, ‘g’, ‘r’, ‘a’, ‘p’, ‘e’]
“`

append()extend() の違いは重要です。append() は引数を1つの要素として追加するのに対し、extend() は引数のイテラブルから要素を取り出してリストの末尾に追加します。

“`python
list_a = [1, 2]
list_b = [3, 4]

list_a.append(list_b)
print(list_a) # 出力: [1, 2, [3, 4]] # list_b がそのまま要素として追加される

list_c = [1, 2]
list_d = [3, 4]
list_c.extend(list_d)
print(list_c) # 出力: [1, 2, 3, 4] # list_d の要素がそれぞれ追加される
“`

7.3. insert() メソッド

リストの指定した位置に要素を挿入するには、insert(インデックス, 要素) メソッドを使います。

“`python
fruits = [“apple”, “banana”, “cherry”]
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’]

インデックス1の位置に “grape” を挿入

fruits.insert(1, “grape”)
print(fruits) # 出力: [‘apple’, ‘grape’, ‘banana’, ‘cherry’] # 元の要素は後ろにずれる

インデックス0の位置 (先頭) に “orange” を挿入

fruits.insert(0, “orange”)
print(fruits) # 出力: [‘orange’, ‘apple’, ‘grape’, ‘banana’, ‘cherry’]

リストの長さ以上のインデックスを指定した場合、末尾に追加されます (append() と同じ挙動)

fruits.insert(100, “kiwi”)
print(fruits) # 出力: [‘orange’, ‘apple’, ‘grape’, ‘banana’, ‘cherry’, ‘kiwi’]
“`

insert() は要素を挿入するため、その位置以降の全ての要素のインデックスが1つずつずれることに注意してください。

7.4. + 演算子によるリストの結合

+ 演算子を使って2つ以上のリストを結合し、新しいリストを作成することができます。

“`python
list1 = [1, 2, 3]
list2 = [4, 5, 6]

リストを結合して新しいリストを作成

combined_list = list1 + list2
print(combined_list) # 出力: [1, 2, 3, 4, 5, 6]

元のリストは変更されません

print(list1) # 出力: [1, 2, 3]
print(list2) # 出力: [4, 5, 6]
“`

+ 演算子を使った結合は、extend() とは異なり、常に新しいリストを生成します。頻繁に要素を追加する場合は append()extend() の方が効率が良いことが多いです。しかし、複数のリストを一度に結合したい場合や、元のリストを変更したくない場合には便利です。

また、* 演算子を使ってリストを指定回数繰り返した新しいリストを作成することもできます。

python
repeated_list = [1, 2] * 3
print(repeated_list) # 出力: [1, 2, 1, 2, 1, 2]

8. リストからの要素の削除

リストから要素を削除する方法もいくつかあります。削除したい要素を「インデックスで指定するか」「値で指定するか」「削除した要素を取得したいか」によって使い分けます。

8.1. del

del 文を使うと、指定したインデックスの要素、または指定したスライスの要素を削除できます。

“`python
fruits = [“apple”, “banana”, “cherry”, “date”, “elderberry”]
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’, ‘date’, ‘elderberry’]

インデックス1の要素 (“banana”) を削除

del fruits[1]
print(fruits) # 出力: [‘apple’, ‘cherry’, ‘date’, ‘elderberry’]

インデックス2から4まで ([date, elderberry]) を削除

del fruits[2:4]
print(fruits) # 出力: [‘apple’, ‘cherry’]

リスト全体を削除

del fruits[:]

print(fruits) # 出力: []

変数自体を削除 (リストにアクセスできなくなる)

del fruits

print(fruits) # -> NameError: name ‘fruits’ is not defined

“`

del は文であり、メソッドではありません。インデックスやスライスを指定して、リストから直接要素を取り除きます。指定したインデックスが存在しない場合は IndexError が発生します。

8.2. remove() メソッド

リストの中から指定した値を持つ最初の要素を削除するには、remove(値) メソッドを使います。

“`python
fruits = [“apple”, “banana”, “cherry”, “banana”]
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’, ‘banana’]

値 “banana” を持つ最初の要素を削除

fruits.remove(“banana”)
print(fruits) # 出力: [‘apple’, ‘cherry’, ‘banana’] # 2つ目の “banana” は残る

存在しない値を削除しようとすると ValueError が発生します

fruits.remove(“grape”) # -> ValueError: list.remove(x): x not in list

“`

remove() は削除したい値がリストの中に存在しない場合、ValueError を発生させます。そのため、remove() を使う前に in 演算子で要素の存在を確認するか、try...except ブロックでエラーを捕捉すると安全です。

python
fruits = ["apple", "banana", "cherry"]
if "grape" in fruits:
fruits.remove("grape")
else:
print("Grape is not in the list.") # 出力: Grape is not in the list.

8.3. pop() メソッド

リストから指定したインデックスの要素を削除し、その要素の値を返却するには、pop(インデックス) メソッドを使います。インデックスを省略した場合、末尾の要素を削除して返却します。

“`python
fruits = [“apple”, “banana”, “cherry”]
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’]

インデックス1の要素 (“banana”) を削除し、その値を取得

removed_fruit = fruits.pop(1)
print(removed_fruit) # 出力: banana
print(fruits) # 出力: [‘apple’, ‘cherry’]

インデックスを省略して末尾の要素 (“cherry”) を削除し、その値を取得

last_fruit = fruits.pop()
print(last_fruit) # 出力: cherry
print(fruits) # 出力: [‘apple’]

空のリストに対して pop() を呼び出すと IndexError が発生します

empty_list = []

empty_list.pop() # -> IndexError: pop from empty list

存在しないインデックスを指定した場合も IndexError が発生します

fruits = [“apple”]

fruits.pop(1) # -> IndexError: pop index out of range

“`

pop() は、リストから要素を取り出しつつ、その値を使って別の処理をしたい場合によく使われます。例えば、スタック(LIFO: 後入れ先出し)やキュー(FIFO: 先入れ先出し)のようなデータ構造をリストで模倣する際に便利です(ただし、キューの場合は collections.deque の方が効率的です)。

9. リストの操作とメソッド(よく使うもの)

リストには、要素の数を調べたり、要素を検索したり、並べ替えたりするための便利なメソッドや関数がいくつか用意されています。

9.1. len() 関数:要素数の取得

リストに含まれる要素の数(長さ)を取得するには、組み込み関数 len() を使います。

“`python
fruits = [“apple”, “banana”, “cherry”]
print(len(fruits)) # 出力: 3

empty_list = []
print(len(empty_list)) # 出力: 0
“`

9.2. in 演算子:要素の存在確認

特定の要素がリストに含まれているかを確認するには、in 演算子を使います。結果は真偽値(TrueまたはFalse)で返されます。

“`python
fruits = [“apple”, “banana”, “cherry”]

print(“banana” in fruits) # 出力: True
print(“grape” in fruits) # 出力: False

含まれていないことを確認するには not in を使います

print(“grape” not in fruits) # 出力: True
“`

この in 演算子は、remove() メソッドを使う前に要素の存在を確認する際などに役立ちます。

9.3. count() メソッド:要素の出現回数をカウント

リストの中で、特定の値を持つ要素がいくつ含まれているかをカウントするには、count(値) メソッドを使います。

“`python
numbers = [1, 2, 2, 3, 4, 2, 5]

print(numbers.count(2)) # 出力: 3
print(numbers.count(1)) # 出力: 1
print(numbers.count(99)) # 出力: 0 (存在しない場合は0)
“`

9.4. index() メソッド:要素のインデックスを取得

リストの中で、指定した値を持つ最初の要素のインデックスを取得するには、index(値) メソッドを使います。

“`python
fruits = [“apple”, “banana”, “cherry”, “banana”]

print(fruits.index(“banana”)) # 出力: 1 # 最初に見つかった “banana” のインデックス

見つからなかった場合、ValueError が発生します

print(fruits.index(“grape”)) # -> ValueError: ‘grape’ is not in list

検索を開始するインデックスと終了するインデックスを指定することもできます

print(fruits.index(“banana”, 2)) # 出力: 3 # インデックス2から検索を開始して見つかった “banana” (2つ目の “banana”)

print(fruits.index(“banana”, 0, 1)) # -> ValueError: ‘banana’ is not in list # インデックス0から1の範囲には “banana” がない

“`

remove() と同様に、index() も値が見つからない場合に ValueError を発生させます。使う前に in 演算子で存在を確認するか、try...except でエラーを捕捉するのが安全です。

9.5. sort() メソッド:リストをインプレースでソート

リストの要素を昇順(小さい順)または降順(大きい順)に並べ替えるには、sort() メソッドを使います。このメソッドは元のリスト自体を変更します(インプレース操作)

“`python
numbers = [5, 2, 8, 1, 9, 4]
print(numbers) # 出力: [5, 2, 8, 1, 9, 4]

昇順にソート (デフォルト)

numbers.sort()
print(numbers) # 出力: [1, 2, 4, 5, 8, 9]

降順にソートするには reverse=True オプションを指定

numbers = [5, 2, 8, 1, 9, 4] # 元に戻す
numbers.sort(reverse=True)
print(numbers) # 出力: [9, 8, 5, 4, 2, 1]

文字列のリストもアルファベット順にソートできます

fruits = [“banana”, “apple”, “cherry”]
fruits.sort()
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’]
“`

異なる型の要素(数値と文字列など)が混在するリストは、通常 sort() できません(TypeErrorが発生します)。

9.6. sorted() 関数:新しいソート済みリストを作成

sort() メソッドとは別に、組み込み関数 sorted(イテラブル) も要素をソートできます。sorted()新しいソート済みのリストを返却し、元のリストは変更しません

“`python
numbers = [5, 2, 8, 1, 9, 4]
print(numbers) # 出力: [5, 2, 8, 1, 9, 4]

sorted() 関数を使って新しいソート済みリストを作成

sorted_numbers = sorted(numbers)
print(sorted_numbers) # 出力: [1, 2, 4, 5, 8, 9]

元のリストは変更されない

print(numbers) # 出力: [5, 2, 8, 1, 9, 4]

sorted() にも reverse=True オプションを指定できます

reverse_sorted_numbers = sorted(numbers, reverse=True)
print(reverse_sorted_numbers) # 出力: [9, 8, 5, 4, 2, 1]

sorted() はリスト以外のイテラブル(タプルやセットなど)にも使えます

my_tuple = (5, 2, 8, 1)
sorted_tuple_as_list = sorted(my_tuple)
print(sorted_tuple_as_list) # 出力: [1, 2, 5, 8]
“`

sort() はリスト専用のメソッドでインプレース操作、sorted() はあらゆるイテラブルに使える関数で新しいリストを返却、という違いを理解しておきましょう。

9.7. reverse() メソッド:リストをインプレースで反転

リストの要素の並び順を反転するには、reverse() メソッドを使います。これも元のリスト自体を変更します(インプレース操作)

“`python
numbers = [1, 2, 3, 4, 5]
print(numbers) # 出力: [1, 2, 3, 4, 5]

numbers.reverse()
print(numbers) # 出力: [5, 4, 3, 2, 1]

reverse() は要素の順序を逆にするだけで、ソートとは異なります

例えば、[1, 5, 3, 2] を reverse() すると [2, 3, 5, 1] になります

mixed_order = [1, 5, 3, 2]
mixed_order.reverse()
print(mixed_order) # 出力: [2, 3, 5, 1]
“`

リストを逆順にした新しいリストを作成したい場合は、スライス [::-1] を使う方が一般的です。

python
numbers = [1, 2, 3, 4, 5]
reversed_numbers = numbers[::-1] # 新しいリストが作成される
print(reversed_numbers) # 出力: [5, 4, 3, 2, 1]
print(numbers) # 出力: [1, 2, 3, 4, 5] # 元のリストは変更されない

9.8. copy() メソッド:リストのシャローコピーを作成

リストのコピーを作成するには、copy() メソッドを使います。これにより、元のリストとは別の、独立したリストが得られます。

“`python
original_list = [1, 2, 3]
copied_list = original_list.copy()

print(original_list) # 出力: [1, 2, 3]
print(copied_list) # 出力: [1, 2, 3]

どちらかのリストを変更しても、もう一方は影響を受けません

copied_list.append(4)
print(original_list) # 出力: [1, 2, 3]
print(copied_list) # 出力: [1, 2, 3, 4]
“`

スライス [:] もリストのコピーを作成できます。original_list[:]original_list.copy() とほぼ同じです。

“`python
original_list = [1, 2, 3]
copied_list_slice = original_list[:]
print(copied_list_slice) # 出力: [1, 2, 3]

スライスによるコピーも独立したリストになります

copied_list_slice.append(4)
print(original_list) # 出力: [1, 2, 3]
print(copied_list_slice) # 出力: [1, 2, 3, 4]
“`

これらのコピーは「シャローコピー(浅いコピー)」と呼ばれます。シャローコピーについては、後ほど「リストのコピーについて」のセクションで詳しく解説します。

9.9. clear() メソッド:リストの全要素を削除

リストの全ての要素を削除して空のリストにするには、clear() メソッドを使います。

“`python
fruits = [“apple”, “banana”, “cherry”]
print(fruits) # 出力: [‘apple’, ‘banana’, ‘cherry’]

fruits.clear()
print(fruits) # 出力: []
“`

これは del fruits[:] と同じ効果を持ちます。

10. リスト内包表記 (リストコンプリヘンション)

リスト内包表記は、Pythonの強力な機能の一つで、リストを生成するための簡潔で読みやすい方法を提供します。既存のリストや他のイテラブルに基づいて、新しいリストを作成する際によく使われます。

基本的な構文は以下の通りです。

python
[式 for 要素変数 in イテラブル]

これは、「イテラブルの各要素に対して繰り返し処理を行い、その要素を変数に代入し、その変数を使って式を評価し、その結果を新しいリストの要素とする」という意味です。

10.1. 基本的なリスト内包表記

例えば、1から5までの整数のリストから、それぞれの2乗のリストを作成してみましょう。

“`python
numbers = [1, 2, 3, 4, 5]

通常のループを使う場合

squares = []
for number in numbers:
squares.append(number ** 2)
print(squares) # 出力: [1, 4, 9, 16, 25]

リスト内包表記を使う場合

squares_comp = [number ** 2 for number in numbers]
print(squares_comp) # 出力: [1, 4, 9, 16, 25]
“`

リスト内包表記を使うと、同じ処理を1行で記述でき、コードがより簡潔になります。

10.2. 条件付きリスト内包表記

リスト内包表記には、要素を新しいリストに含めるかどうかを判断するための if 節を追加することもできます。

構文は以下の通りです。

python
[式 for 要素変数 in イテラブル if 条件式]

例えば、1から10までの整数のうち、偶数のみを抽出してリストを作成してみましょう。

“`python

通常のループを使う場合

even_numbers = []
for i in range(1, 11):
if i % 2 == 0:
even_numbers.append(i)
print(even_numbers) # 出力: [2, 4, 6, 8, 10]

リスト内包表記を使う場合

even_numbers_comp = [i for i in range(1, 11) if i % 2 == 0]
print(even_numbers_comp) # 出力: [2, 4, 6, 8, 10]
“`

if 節は for 節の後に記述します。これは、「イテラブルの各要素に対して繰り返し処理を行い、条件式が真である場合にのみ、その要素を変数に代入し、その変数を使って式を評価し、その結果を新しいリストの要素とする」という意味になります。

10.3. 条件付き式を使ったリスト内包表記

リスト内包表記のの部分には、条件式を含めることもできます。これにより、要素の値自体を条件によって変更することができます。

構文は以下の通りです。

python
[条件が真の場合の式 if 条件式 else 条件が偽の場合の式 for 要素変数 in イテラブル]

これは、「イテラブルの各要素に対して繰り返し処理を行い、条件式が真であれば『条件が真の場合の式』を評価し、偽であれば『条件が偽の場合の式』を評価し、その結果を新しいリストの要素とする」という意味です。

注意: ifelse を式の部分に含める場合、for 節よりも前に書きます。これは、先ほどの「要素を含めるかどうかの条件」とは別の構文です。

例えば、1から10までの整数のうち、偶数ならそのまま、奇数なら0としてリストを作成してみましょう。

“`python

通常のループを使う場合

result = []
for i in range(1, 11):
if i % 2 == 0:
result.append(i)
else:
result.append(0)
print(result) # 出力: [0, 2, 0, 4, 0, 6, 0, 8, 0, 10]

リスト内包表記を使う場合

result_comp = [i if i % 2 == 0 else 0 for i in range(1, 11)]
print(result_comp) # 出力: [0, 2, 0, 4, 0, 6, 0, 8, 0, 10]
“`

ifelse を式の部分に含める場合は、必ず両方記述する必要があります。一方だけを記述することはできません。

10.4. ネストしたリスト内包表記

複数の for 節を組み合わせることで、ネストしたリスト(リストのリストなど)を生成したり、複数のイテラブルを組み合わせてリストを作成したりすることもできます。

構文は以下の通りです。

python
[式 for 要素変数1 in イテラブル1 for 要素変数2 in イテラブル2 ...]

これは、内側の for ループを外側の for ループよりも先に書く、通常のネストしたループの書き方とは逆になります。

例えば、2つのリスト [1, 2]['a', 'b'] から、要素のすべての組み合わせを含むリストを作成してみましょう。

“`python
list1 = [1, 2]
list2 = [‘a’, ‘b’]

通常のループを使う場合

combined = []
for x in list1:
for y in list2:
combined.append((x, y)) # 要素をタプルとして追加
print(combined) # 出力: [(1, ‘a’), (1, ‘b’), (2, ‘a’), (2, ‘b’)]

リスト内包表記を使う場合

combined_comp = [(x, y) for x in list1 for y in list2]
print(combined_comp) # 出力: [(1, ‘a’), (1, ‘b’), (2, ‘a’), (2, ‘b’)]
“`

ネストしたリスト内包表記は複雑になりがちなので、可読性が損なわれる場合は通常のループを使った方が良いこともあります。

10.5. リスト内包表記のメリット

  • 簡潔さ: 短いコードでリストを作成できます。
  • 可読性: 慣れると、通常のループで同じことをするよりも意図が伝わりやすくなります。
  • 効率: 多くの場合、通常の for ループと append() を使うよりも高速に動作します。

最初は難しく感じるかもしれませんが、様々な例に触れていくうちに慣れてきます。ぜひ積極的に使ってみてください。

11. リストのコピーについて(シャローコピー vs ディープコピー)

Pythonでリストを扱う上で、コピーについて理解しておくことは非常に重要です。特に、ネストしたリストを扱う際には注意が必要です。

python
list_a = [1, 2, 3]
list_b = list_a # 代入

このように = 演算子で代入した場合、新しいリストが作成されるわけではありません。list_blist_a同じリストオブジェクトを参照しているだけです。

“`python
list_a = [1, 2, 3]
list_b = list_a
print(list_a) # 出力: [1, 2, 3]
print(list_b) # 出力: [1, 2, 3]

list_b を変更すると list_a も変更されます

list_b.append(4)
print(list_a) # 出力: [1, 2, 3, 4]
print(list_b) # 出力: [1, 2, 3, 4]

id() 関数でオブジェクトの識別番号を確認できます

print(id(list_a))
print(id(list_b)) # list_a と同じ識別番号が出力されるはずです
“`

このように、参照をコピーしただけでは、どちらかのリストを変更するともう一方にも影響が出てしまいます。独立したリストのコピーを作成したい場合は、以下の方法を使います。

11.1. シャローコピー (浅いコピー)

list.copy() メソッドまたはスライス [:] を使うと、リストのシャローコピーを作成できます。

“`python
list_a = [1, 2, 3]
list_copy1 = list_a.copy() # copy() メソッド
list_copy2 = list_a[:] # スライス

print(id(list_a))
print(id(list_copy1)) # list_a とは異なる識別番号が出力される
print(id(list_copy2)) # list_a とは異なる識別番号が出力される

list_a を変更してもコピーには影響しない

list_a.append(4)
print(list_a) # 出力: [1, 2, 3, 4]
print(list_copy1) # 出力: [1, 2, 3] (影響なし)
print(list_copy2) # 出力: [1, 2, 3] (影響なし)
“`

これで、リスト自体はコピーされて独立しました。しかし、シャローコピーは「浅い」コピーです。これは、リストの要素がミュータブルなオブジェクト(リストの中に別のリストがあるなど)である場合に問題となることがあります。

シャローコピーは、リストに含まれる各要素の参照をコピーします。もし要素が別のリストのようなミュータブルなオブジェクトであれば、コピーされたリストと元のリストは同じミュータブルオブジェクトを参照していることになります。

“`python
original_list = [[1, 2], [3, 4]]
shallow_copy = original_list.copy() # シャローコピー

print(original_list) # 出力: [[1, 2], [3, 4]]
print(shallow_copy) # 出力: [[1, 2], [3, 4]]

最上位のリストオブジェクトは別々

print(id(original_list))
print(id(shallow_copy)) # 異なるID

内包されているリストオブジェクトは同じ

print(id(original_list[0]))
print(id(shallow_copy[0])) # 同じID!
print(id(original_list[1]))
print(id(shallow_copy[1])) # 同じID!

シャローコピーの内包リストを変更してみる

shallow_copy[0].append(99)
print(original_list) # 出力: [[1, 2, 99], [3, 4]] # 元のリストも変更された!
print(shallow_copy) # 出力: [[1, 2, 99], [3, 4]]

シャローコピーの要素を別のオブジェクトに置き換える場合は影響しない

shallow_copy[1] = [5, 6]
print(original_list) # 出力: [[1, 2, 99], [3, 4]] # 元のリストは影響なし
print(shallow_copy) # 出力: [[1, 2, 99], [5, 6]]
“`

このように、シャローコピーでは、リストの要素がミュータブルなオブジェクトの場合、そのミュータブルオブジェクトはコピーされず、参照が共有されてしまいます。

11.2. ディープコピー (深いコピー)

ネストしたリストなど、リストの要素がミュータブルなオブジェクトである場合でも、完全に独立したコピーを作成したい場合は、ディープコピー(深いコピー)を使います。ディープコピーは、リストだけでなく、リストに含まれる全てのミュータブルなオブジェクトも再帰的にコピーします。

ディープコピーを行うには、copy モジュールの deepcopy() 関数を使います。

“`python
import copy

original_list = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(original_list) # ディープコピー

print(original_list) # 出力: [[1, 2], [3, 4]]
print(deep_copy) # 出力: [[1, 2], [3, 4]]

最上位のリストオブジェクトは別々

print(id(original_list))
print(id(deep_copy)) # 異なるID

内包されているリストオブジェクトも別々!

print(id(original_list[0]))
print(id(deep_copy[0])) # 異なるID!
print(id(original_list[1]))
print(id(deep_copy[1])) # 異なるID!

ディープコピーの内包リストを変更しても、元のリストは影響を受けない

deep_copy[0].append(99)
print(original_list) # 出力: [[1, 2], [3, 4]] # 元のリストは変更されない
print(deep_copy) # 出力: [[1, 2, 99], [3, 4]]
“`

ディープコピーは、複雑な構造を持つリストを完全に独立させたい場合に必要になります。ただし、ディープコピーはシャローコピーよりも処理に時間がかかる場合があります。リストの要素が全てイミュータブルなオブジェクト(数値、文字列、タプルなど)であれば、シャローコピーでもディープコピーでも挙動は同じです。

ほとんどの場合、単にリスト全体のコピーが欲しいだけであれば、copy()[:] によるシャローコピーで十分です。ネストしたミュータブルなオブジェクトが含まれており、それらも独立させたい場合にのみ deepcopy() を検討しましょう。

12. リストと他のデータ構造

Pythonにはリスト以外にも複数のデータを格納するデータ構造があります。リストの特徴をより深く理解するために、他の代表的なデータ構造と比較してみましょう。

12.1. リスト vs タプル (tuple)

  • リスト (list): [] で定義。変更可能 (ミュータブル)。要素の追加、削除、変更が可能。
  • タプル (tuple): () で定義。変更不可能 (イミュータブル)。作成後に要素を変更、追加、削除することはできない。

“`python
my_list = [1, 2, 3]
my_tuple = (1, 2, 3)

my_list[0] = 100 # OK
print(my_list) # 出力: [100, 2, 3]

my_tuple[0] = 100 # -> TypeError: ‘tuple’ object does not support item assignment

“`

タプルは、変更されるべきではないデータの集まりや、辞書のキーとして使いたい場合(リストはミュータブルなので辞書のキーにはなれない)などに適しています。タプルの方がリストよりもわずかに処理速度が速く、メモリ効率が良い傾向があります。

12.2. リスト vs セット (set)

  • リスト (list): [] で定義。順序がある重複を許す変更可能
  • セット (set): {} または set() で定義。順序がない(インデックスでアクセスできない)。重複する要素を持たない変更可能(要素の追加・削除はできるが、要素の値自体は変更できない)。

“`python
my_list = [1, 2, 2, 3, 1]
my_set = {1, 2, 2, 3, 1}

print(my_list) # 出力: [1, 2, 2, 3, 1] # 順序があり重複がある
print(my_set) # 出力例: {1, 2, 3} # 順序がなく重複が削除される

print(my_set[0]) # -> TypeError: ‘set’ object is not subscriptable (インデックスでアクセスできない)

セットは集合演算 (和集合、積集合、差集合など) が得意

set1 = {1, 2, 3}
set2 = {3, 4, 5}
print(set1 | set2) # 和集合: {1, 2, 3, 4, 5}
print(set1 & set2) # 積集合: {3}
“`

セットは、要素の重複を取り除きたい場合や、要素の存在チェックを高速に行いたい場合 (in 演算子が非常に速い) に適しています。

12.3. リスト vs 辞書 (dict)

  • リスト (list): [] で定義。順序があるインデックス(0からの整数)を使って要素にアクセス。
  • 辞書 (dict): {} で定義。{キー: 値, ...} の形式。順序がある (Python 3.7+)。キーを使って値にアクセス。キーは重複しない変更可能

“`python
my_list = [“apple”, “banana”, “cherry”]
my_dict = {“red”: “apple”, “yellow”: “banana”, “reddish”: “cherry”}

リストはインデックスでアクセス

print(my_list[0]) # 出力: apple

辞書はキーでアクセス

print(my_dict[“yellow”]) # 出力: banana

辞書に新しい要素を追加

my_dict[“green”] = “lime”
print(my_dict) # 出力例: {‘red’: ‘apple’, ‘yellow’: ‘banana’, ‘reddish’: ‘cherry’, ‘green’: ‘lime’}
“`

辞書は、特定の名前(キー)とそれに対応するデータ(値)を関連付けて管理したい場合に最適です。リストはデータの順序が重要で、その位置に基づいてアクセスしたい場合に適しています。

それぞれのデータ構造は異なる強みを持っているため、解決したい問題に応じて適切なものを選ぶことが重要です。リストは、順序付きで変更可能な複数のデータの集まりを扱う際の、まず最初に検討すべき基本的な選択肢と言えるでしょう。

13. リスト操作の注意点

リストは非常に柔軟で便利なデータ構造ですが、操作によっては予期しない挙動をすることがあります。特に初心者が陥りやすい注意点について解説します。

13.1. ループ中にリストを変更することの危険性

for ループを使ってリストを繰り返し処理している最中に、そのリストに対して要素の追加や削除を行うと、ループの挙動がおかしくなることがあります。

例えば、リストから特定の条件を満たす要素を削除したい場合を考えます。

“`python
numbers = [1, 2, 3, 4, 5, 6]

リストから偶数を削除したい(危険な例!)

for number in numbers:
if number % 2 == 0:
numbers.remove(number)
print(numbers) # 期待値: [1, 3, 5]

実際の出力: [1, 3, 5]

この例はうまくいったように見えますが、複雑なケースではバグの元になります。

例を変えてみましょう。

numbers = [1, 2, 2, 3, 4]

リストからすべての 2 を削除したい(危険な例!)

for number in numbers:
print(f”処理中の要素: {number}, リスト: {numbers}”)
if number == 2:
numbers.remove(number)

print(numbers) # 期待値: [1, 3, 4]

実際の出力: 処理中の要素: 1, リスト: [1, 2, 2, 3, 4]

処理中の要素: 2, リスト: [1, 2, 2, 3, 4]

処理中の要素: 3, リスト: [1, 2, 3, 4]

出力: [1, 3, 4]

この場合も見た目はうまくいきましたが、最初の 2 を削除した後、リストは [1, 2, 3, 4] になります。

ループは次の要素(元のリストのインデックス1の要素、つまり現在のリストのインデックス1の要素である 2)に進みます。

しかし、削除によって要素がずれているため、元のリストのインデックス2にあったはずの 2 をスキップしてしまいます。

もっと分かりやすい例。

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

インデックスが偶数の要素を削除したい(危険な例!)

for index in range(len(numbers)):
print(f”処理中のインデックス: {index}, リスト: {numbers}”)
if index % 2 == 0:
del numbers[index]

print(numbers) # 期待値: [2, 4]

実際の出力: 処理中のインデックス: 0, リスト: [1, 2, 3, 4, 5]

処理中のインデックス: 1, リスト: [2, 3, 4, 5] # インデックス0の 1 が削除され、要素がずれた

処理中のインデックス: 2, リスト: [2, 4, 5] # インデックス1の 3 が削除され、要素がずれた

処理中のインデックス: 3, リスト: [2, 4, 5] # 存在しないインデックス3にアクセスしようとする (for range(4) のまま)

出力: [2, 4, 5] # 想定外の結果になる!

“`

このように、ループ中にリストの要素数やインデックスが変化すると、思わぬ要素がスキップされたり、IndexError が発生したりする可能性があります。

ループ中にリストを変更したい場合は、以下のいずれかの安全な方法を使いましょう。

  1. 新しいリストを作成する: 条件に合う要素だけを集めた新しいリストを作成します。これはリスト内包表記を使うのに最適な場面です。

    “`python
    numbers = [1, 2, 2, 3, 4]

    2以外の要素だけを集めた新しいリストを作成

    new_numbers = [number for number in numbers if number != 2]
    print(new_numbers) # 出力: [1, 3, 4]
    “`

  2. 元のリストのコピーをイテレートする: リストのコピーに対してループを実行し、元のリストを変更します。

    “`python
    numbers = [1, 2, 2, 3, 4]

    コピーに対してループ

    for number in numbers.copy(): # または numbers[:]
    print(f”処理中の要素: {number}, リスト: {numbers}”)
    if number == 2:
    numbers.remove(number) # 元のリストから削除

    print(numbers) # 出力: [1, 3, 4] # 想定通り
    “`

  3. 逆順にイテレートする: インデックスを使って逆順にループすると、要素を削除してもまだ処理していない前のインデックスには影響が出ません。

    “`python
    numbers = [1, 2, 3, 4, 5]

    インデックスが偶数の要素を逆順に削除

    最後のインデックスから0まで、ステップ-1

    for index in range(len(numbers) – 1, -1, -1):
    print(f”処理中のインデックス: {index}, リスト: {numbers}”)
    if index % 2 == 0:
    del numbers[index]

    print(numbers) # 出力: [2, 4] # 想定通り
    “`

リストを繰り返し処理して要素を変更・削除する場合は、これらの安全な方法を必ず使うようにしてください。

13.2. ミュータブルな要素を持つ場合の注意点 (再掲)

「リストのコピーについて」のセクションで説明したシャローコピーとディープコピーの違いは、リスト自体を変更するのではなく、リストに含まれるミュータブルな要素を変更する場合にも関連してきます。

“`python
list_a = [[1, 2], [3, 4]]
list_b = list_a # 参照の共有

list_b の内包リストを変更

list_b[0].append(99)

print(list_a) # 出力: [[1, 2, 99], [3, 4]] # list_a も変更される!
print(list_b) # 出力: [[1, 2, 99], [3, 4]]
“`

これは、list_alist_b が同じ内包リストオブジェクト [1, 2] を参照しているためです。要素がミュータブルなオブジェクトである場合、その要素を変更する操作は、そのオブジェクトを共有している他の変数にも影響を及ぼします。

これを防ぐには、ディープコピーを使用するか、または要素を操作する前にその要素自体をコピーしてから変更するなどの工夫が必要です。

13.3. 大きなリストを扱う際のパフォーマンス

リストは非常に便利ですが、要素数が非常に多いリストに対して特定の操作を行う場合、パフォーマンスに注意が必要なことがあります。

  • insert(0, element): リストの先頭に要素を挿入する場合、既存の全ての要素を1つずつ後ろにずらす必要があるため、リストの長さに比例して時間がかかります(O(n))。末尾への追加 append() は通常非常に高速です(O(1))。
  • remove(value): 指定した値を持つ要素を探して削除する場合、最悪の場合リスト全体を走査する必要があるため、リストの長さに比例して時間がかかります(O(n))。
  • del list[index]: 指定したインデックスの要素を削除する場合も、その位置以降の要素をずらす必要があるため、O(n) の時間がかかります。
  • index(value): 指定した値を持つ要素を探す場合も、最悪の場合リスト全体を走査するため O(n) です。
  • in 演算子: 要素の存在チェックも、リストの場合は O(n) です。大量の要素に対する存在チェックを頻繁に行う場合は、セット(O(1))や辞書(O(1))の利用を検討しましょう。

ほとんどの日常的なプログラミングにおいては、これらのパフォーマンスの違いを気にする必要はありません。しかし、何万、何十万といった要素を持つリストを扱う場合や、処理速度が非常に重要な場面では、リストの操作の計算量(ビッグオー記法で表される)を意識することが大切です。

14. 実践例と応用

これまで学んできたリストの基本操作を使って、いくつかの簡単な実践例を見てみましょう。

14.1. 簡単なデータ処理:合計、平均、フィルタリング

数字のリストから、合計、平均、特定の条件を満たす要素(例えば偶数)を取り出す処理はよく行われます。

“`python
grades = [85, 92, 78, 90, 88, 76, 95, 89, 82]

合計の計算

total = sum(grades) # sum() 関数はリストの合計値を計算します
print(f”合計点: {total}”) # 出力: 合計点: 775

平均点の計算

average = total / len(grades)
print(f”平均点: {average:.2f}”) # 出力: 平均点: 86.11

85点以上の点数をフィルタリング (リスト内包表記を使用)

high_grades = [grade for grade in grades if grade >= 85]
print(f”85点以上の点数: {high_grades}”) # 出力: [85, 92, 90, 88, 95, 89]

偶数の点数だけを抽出 (リスト内包表記を使用)

even_grades = [grade for grade in grades if grade % 2 == 0]
print(f”偶数の点数: {even_grades}”) # 出力: [92, 90, 88, 76, 82]
“`

14.2. 文字列リストの操作

文字列のリストに対して、特定の条件でフィルタリングしたり、変換したりする例です。

“`python
words = [“apple”, “banana”, “cherry”, “date”, “elderberry”]

5文字以上の単語だけを抽出

long_words = [word for word in words if len(word) >= 5]
print(f”5文字以上の単語: {long_words}”) # 出力: [‘apple’, ‘banana’, ‘cherry’, ‘elderberry’]

全ての単語を大文字に変換

upper_words = [word.upper() for word in words]
print(f”大文字に変換: {upper_words}”) # 出力: [‘APPLE’, ‘BANANA’, ‘CHERRY’, ‘DATE’, ‘ELDERBERRY’]

特定の文字 (‘a’) を含む単語を抽出

words_with_a = [word for word in words if ‘a’ in word]
print(f”‘a’を含む単語: {words_with_a}”) # 出力: [‘apple’, ‘banana’, ‘date’]
“`

これらの例のように、リスト内包表記はフィルタリングや変換といったデータ処理に非常に役立ちます。

14.3. リストを使った簡単なアルゴリズムの基礎

リストは様々なアルゴリズムを実装する際の基本的なツールとなります。例えば、簡単な探索やソートの概念を理解するためにリストを使うことができます。

  • 線形探索(Linear Search): リストの要素を最初から順番に調べて、探している要素を見つけるアルゴリズムです。

    “`python
    def linear_search(my_list, target):
    for index in range(len(my_list)):
    if my_list[index] == target:
    return index # 見つかったらそのインデックスを返す
    return -1 # 見つからなかったら -1 を返す

    numbers = [10, 5, 15, 20, 25]
    print(linear_search(numbers, 15)) # 出力: 2
    print(linear_search(numbers, 30)) # 出力: -1
    “`

    もちろん、Pythonでは list.index()in 演算子を使う方が一般的で効率的ですが、これは探索アルゴリズムの最も基本的な考え方を示しています。

  • バブルソート(Bubble Sort)の概念: 隣り合う要素を比較し、順番が逆であれば交換する操作を繰り返してリストをソートするアルゴリズムです。非効率ですが、ソートの概念を理解しやすいアルゴリズムの一つです。

    “`python
    def bubble_sort(my_list):
    n = len(my_list)
    # リストの長さ-1 回繰り返す
    for i in range(n – 1):
    # 各パスで最大要素が末尾に移動する
    for j in range(n – 1 – i):
    # 隣り合う要素を比較
    if my_list[j] > my_list[j+1]:
    # 順番が逆なら交換
    my_list[j], my_list[j+1] = my_list[j+1], my_list[j]
    return my_list

    numbers = [5, 2, 8, 1, 9, 4]
    print(bubble_sort(numbers)) # 出力: [1, 2, 4, 5, 8, 9]
    “`

    実際のPythonプログラミングでは、前述の list.sort()sorted() 関数を使うべきですが、リストを使ってこのようなアルゴリズムを実装してみることは、計算論的な思考を養う上で非常に良い練習になります。

リストは、単純なデータ処理から複雑なアルゴリズムの実装まで、Pythonプログラミングの様々な場面で活用される非常に重要なツールです。

15. まとめ

お疲れ様でした!この記事では、Pythonのリストについて、その基本的な定義から作成方法、要素へのアクセス、変更、追加、削除、様々な操作メソッド、リスト内包表記、コピーの概念、他のデータ構造との比較、そして注意点まで、ゼロから徹底的に解説してきました。

リストはPythonプログラミングの土台となる要素の一つです。複数のデータをまとめて効率的に扱うためには、リストの操作をマスターすることが不可欠です。

この記事で学んだことの重要なポイントを振り返りましょう。

  • リストは [] で表現され、複数の要素を順序付けて格納する、変更可能なデータ構造です。
  • インデックス(0始まり)やスライスを使って要素にアクセス・取得できます。
  • リストはミュータブルであり、作成後に要素の代入、追加(append, extend, insert, +)、削除(del, remove, pop, = [])が可能です。
  • len(), in, count(), index(), sort(), sorted(), reverse(), copy(), clear() など、便利な組み込み関数やメソッドが多数あります。
  • リスト内包表記を使えば、リストの生成や変換を簡潔に記述できます。
  • リストのコピーにはシャローコピー(copy(), [:])とディープコピー(copy.deepcopy())があり、ネストしたミュータブルな要素の扱いに違いがあります。
  • ループ中のリスト変更や、ミュータブルな要素の共有には注意が必要です。
  • リストは、簡単なデータ処理からアルゴリズムの実装まで幅広く応用できます。

リストの操作は、実際にコードを書いて動かしながら学ぶのが一番効果的です。この記事で紹介したコード例を参考に、ご自身で様々なパターンを試してみてください。エラーが発生したら、そのエラーメッセージをよく読んで、何が問題なのかを理解するように努めましょう。

リストの基本をしっかり押さえれば、その後のPython学習(関数、クラス、モジュール、ライブラリなど)が格段に進めやすくなります。

Pythonリストの世界は、まだ奥が深いです。例えば、ジェネレーター式との組み合わせや、高階関数(map, filter, reduce)とリストとの連携、標準ライブラリの collections モジュールにある dequeCounter といったリストに関連するデータ構造など、さらに学ぶべきことはたくさんあります。

しかし、今日学んだ基本があれば、それらを理解するための土台はしっかりとできています。

このガイドが、皆さんのPython学習の一助となれば幸いです。リストを使いこなして、様々なプログラムを書いてみてください!応援しています!

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール