Skip to content
This page has been auto-translated and may contain errors.View in English

リスト

変数は1つの値しか保持できません。リストは複数の値を順番に、1つの名前で保持します。リーダーボードは順位付けされたスコアの並びです。クイズは質問の集まりです。関連する値のグループを管理する必要が出てきたら、リストが必要です。

リストはPythonの汎用的な順序付きミュータブルシーケンスです。時間とともに変化するもの、つまり項目の追加や削除、順序の入れ替え、内容のフィルタリングやソートが必要なものに自然にフィットします。順序が重要でコレクションが変化する場合、通常リストが最初の正しい選択です。

list はPythonの動的配列です。順序付きでミュータブルなシーケンスであり、連続したヒープ割り当てに支えられています。ランダムアクセスは O(1) です。append() は配列が余分に割り当てられ、オーバーフロー時に拡張されるため、償却 O(1) です。insert()remove() は後続の要素をシフトするため O(n) です。これらのコストは、他のデータ構造を好むべきかどうかの判断材料になります。

リストの作成

角括弧の中に、カンマで区切られた値を並べます。リストはあらゆる型を混在させて保持でき、空のリストも有効で、後から徐々に作り上げる出発点としてよく使われます。

リストは括弧構文で定義され、挿入順序を保持します。他のリストを含む任意のPython値を保持できます。空のリスト [] は、項目を段階的に積み上げる際の標準的な出発点です。

括弧リテラルは、いくらかの事前割り当て容量とともに新しい list オブジェクトをヒープ上に割り当てます。要素は任意のPythonオブジェクトであり得ます。リストは値ではなく参照を格納します。異種の要素型は有効ですが、簡単なスクリプト以外では実際にはあまり一般的ではありません。

python
scores   = [87, 92, 74, 65, 91]
players  = ["さくら", "ひろし", "ゆうこ"]
mixed    = ["さくら", 87, True, 3.14]   # 任意の型、ただし一般的ではない
empty    = []

インデックスとスライス

リストは文字列と同じ番号付けを使います。位置は0から始まり、負の数は末尾から数えます。任意の項目を位置で読み取れます。リストはミュータブルなので、特定の位置に書き込むこともできます。

リストのインデックスとスライスは文字列と同じルールに従います。重要な違いはミュータビリティです。インデックスやスライスに代入することで、その場で項目を変更できますが、これは文字列では許されません。

list.__getitem__ は整数と slice オブジェクトを受け付け、str と同じクランプ規則に従います。__setitem__ は項目の代入を可能にします。スライス代入は要素の範囲を置き換え、置換が異なる長さの場合はリストのサイズを変更できます。lst[1:3] = [10, 20, 30] は2つの項目を3つで置き換えます。

python
scores = [87, 92, 74, 65, 91]

scores[0]      # 87  (最初)
scores[-1]     # 91  (最後)
scores[1:3]    # [92, 74]
scores[:2]     # [87, 92]
scores[::-1]   # [91, 65, 74, 92, 87]  (逆順)

scores[0] = 90   # ミュータブル: 動作する (文字列ではTypeErrorとなる)

項目の追加

項目を追加する3つのメソッドがあります。append() は末尾に1つの項目を追加し、ほとんどの場合これを使うことになります。insert() は特定の位置に追加します。extend() は別のリストを統合します。

append() は償却 O(1) で、項目を1つずつ追加してリストを構築する標準的な方法です。insert() は後続の要素をシフトするため O(n) です。extend()+= と同等で、ループ内で append() を繰り返すよりも効率的です。

append() は事前割り当てバッファを使用し、オーバーフローによってサイズ変更がトリガーされた場合にのみコピーします。成長係数は最初の数回のサイズ変更後に約1.125倍であり、償却 O(1) を実現します。insert(0, x) は O(n) です。すべての要素が右にシフトします。頻繁な先頭挿入には、collections.deque が O(1) の appendleft を提供します。extend(iterable)__iter__ を一度呼び出し、一度の操作で拡張します。

python
scores = [87, 92, 74]

scores.append(65)          # [87, 92, 74, 65]
scores.insert(1, 100)      # [87, 100, 92, 74, 65]
scores.extend([55, 71])    # [87, 100, 92, 74, 65, 55, 71]

よくある間違い: リストを append() すると、そのリスト全体が1つの項目として追加され、リストの中にリストができてしまいます。代わりに extend() を使って統合しましょう:

append(x) は常に x を単一の要素として追加します。リストを append() に渡すと、ネストされたリストになります。別のリストのすべての項目をこのリストに統合したい場合は extend() を使います:

append(x) は型に関わらず x を単一のオブジェクトとして list_append を呼び出します。extend(iterable) は引数に対して __iter__ を呼び出し、各要素を個別に追加します。+= 演算子は __iadd__ を呼び出し、内部的には extend を呼び出します。

python
scores.append([55, 71])    # [..., [55, 71]]  ネストされたリスト、おそらく間違い
scores.extend([55, 71])    # [..., 55, 71]    統合された、正解

項目の削除

項目を削除する4つのツールがあります。remove() は値で検索します。pop() は位置で削除し、その項目を返してくれます。del は位置で削除しますが、戻り値はありません。clear() はリスト全体を空にします。

remove() は O(n) です。値による最初の出現を検索します。引数なしの pop() は最後の項目に対しては O(1) です。他の位置に対する pop(i) は要素がシフトするため O(n) です。del scores[i]pop(i) と同等ですが、戻り値を破棄します。

remove(value) は一致が見つかるまで各要素に対して __eq__ を呼び出し、その後すべての後続要素を左にシフトします: O(n)。pop(-1) は O(1) で、シフトは不要です。他のインデックスに対する pop(i) は O(n) です。任意の位置からの頻繁な削除には、データの再構成や別のコレクションの使用を検討してください。

python
scores = [87, 92, 74, 65, 91]

scores.remove(74)    # 74の最初の出現を削除
scores.pop()         # 最後の項目を削除して返す (91)
scores.pop(0)        # 位置0の項目を削除して返す (87)
del scores[1]        # 位置1で削除、戻り値なし
scores.clear()       # すべてを削除

remove() は値がリストにない場合 ValueError を発生させます。確信が持てない場合は、まず in でチェックしましょう:

python
if 74 in scores:
    scores.remove(74)

remove() はミスがあると ValueError を発生させます。in チェックは追加の O(n) スキャンになります。2回のパスを行います。一度きりのコードではこれで問題ありません。try/except ValueError による適切なエラー処理は ファイルと例外 の章で扱います。

in + remove() パターンは2つの O(n) スキャンです。順序を保持する必要がない場合、ターゲットを最後の要素と入れ替えてポップする方が高速です: O(1)。O(1) ルックアップによる集合メンバーシップには、list ではなく set を使ってください。タプル、セット の章で扱います。

ソート

sorted() は全く新しいソートされたリストを返し、元のリストはそのままにします。.sort() はリストをその場でソートし、None を返します。その違いは聞こえる以上に重要です。

sorted() は安全なデフォルトです。元のリストを変更することはありません。.sort() はその場で変更し、None を返します。これはよくある落とし穴です。.sort() の結果を代入すると、ソートされたリストではなく None が得られます。元のリストを保持する必要があるときは sorted() を使い、ソートされたバージョンだけが欲しいときは .sort() を使いましょう。

どちらも Timsort を使います。マージソートと挿入ソートのハイブリッドで、最悪 O(n log n)、ほぼソート済みのデータでは O(n) です。Timsort は安定です。等しい要素は元の相対順序を保持します。.sort() は意図的に None を返します(コマンドとクエリの分離)。sorted() はリストだけでなく任意のイテラブルを受け付け、常にリストを返します。

python
scores = [87, 42, 96, 55, 71]

ranked = sorted(scores)            # [42, 55, 71, 87, 96] (新しいリスト)
scores.sort()                      # その場でソート、Noneを返す
scores.sort(reverse=True)          # [96, 87, 71, 55, 42]

result = scores.sort()             # resultはNoneで、ソートされたリストではない

便利な操作

Pythonにはリストに直接動作する組み込みツールのセットがあります。len()sum()min()max() の4つは常に使うことになるでしょう。

組み込みのシーケンス関数は任意のリストで動作します。in はリストに対しては線形スキャンです。高速な繰り返しメンバーシップテストが必要な場合は、集合に変換しましょう。.index() は値が見つからない場合 ValueError を発生させます。

len()sum()min()max() はすべて __iter__ を呼び出し、リストだけでなく任意のイテラブルで動作します。in はリストに対しては O(n) です。O(1) ルックアップには set を使ってください。.index(value) も O(n) です。sum() は start=0 がデフォルトで、文字列の連結はサポートしません。文字列には "".join() を使ってください。

python
scores = [87, 92, 74, 65, 91]

len(scores)          # 5
sum(scores)          # 409
min(scores)          # 65
max(scores)          # 92
scores.count(87)     # 1
scores.index(74)     # 2
74 in scores         # True
74 not in scores     # False
scores.copy()        # シャローコピー
scores.reverse()     # その場で反転

反復処理

for ループはリストを一度に1項目ずつ処理します。for の後の変数は、各項目を順番に受け取ります。位置も必要な場合は、enumerate() が手動カウンタなしで両方を提供してくれます。

for item in list はリストのイテレータを呼び出し、各ステップでそれを進めます。enumerate(iterable, start=0) はイテレータをラップし、(index, value) のペアを生成します。enumerate() を使う方が、カウンタ変数を維持するよりもクリーンでエラーが起きにくいです。

foriter(list) を呼び出して list_iterator を取得し、StopIteration まで next() を呼び出します。enumerate() は任意のイテレータをラップし、(i, value) のペアを生成します。start パラメータはカウンタをオフセットしますが、基礎となるインデックスには影響しません。enumerate がタプルを生成するため、i, item = pair のアンパックが機能します。

python
players = ["さくら", "ひろし", "ゆうこ"]

for player in players:
    print(player)

for i, player in enumerate(players, start=1):
    print(f"{i}. {player}")
# 1. さくら
# 2. ひろし
# 3. ゆうこ

forループとenumerate

forenumerate()制御フロー の章で完全に扱います。短く言うと: for player in players は項目ごとに1回実行され、enumerate() は反復ごとに位置と値の両方を提供します。

ネストされたリスト

リストは他のリストを含むことができます。これがグリッドやテーブルを表現する方法です。行のリストで、各行が値のリストです。2組の角括弧で項目にアクセスします。最初は行を選び、2番目は列を選びます。

ネストされたリストはリスト参照のリストです。各内部リストは独立したオブジェクトです。連続した添字でアクセスします: grid[row][col]。内部リストを変更すると外部リストも影響を受けます。外部リストは同じオブジェクトへの参照を保持しているからです。

ネストされたリストは真の2次元配列ではありません。外部リストはオブジェクト参照を保持し、内部リストは異なる長さや型である可能性があります。アクセスは2つの __getitem__ 呼び出しを連鎖させます。ネストされたリストのシャローコピーは外部コンテナをコピーしますが内部リストはコピーしません。内部リストへの変更は両方のコピーに影響します。

python
grid = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]

grid[0]       # [1, 2, 3]
grid[1][2]    # 6  (行1、列2)

ミュータビリティ: 落とし穴

これはほぼ全員が驚きます。リストを新しい変数に代入してもコピーは作られません。両方の名前は同じリストを指します。一方を変更すると、もう一方も変わります。独立したコピーを得るには、明示的にそれを求める必要があります。

リストの代入は参照をコピーし、オブジェクトはコピーしません。両方の名前は同じ基礎となるリストを指します。どちらの名前を通じての変更も同じデータに影響します。独立したデータが必要な場合は、.copy()list()、または完全スライス [:] で明示的にコピーしてください。

b = a は2番目の名前を同じリストオブジェクトにバインドします。b を介した変更は a も指しているオブジェクトを変更します。.copy()a[:]シャローコピー を作成します。同じ要素参照を持つ新しいリストオブジェクトです。イミュータブルな値のフラットなリストではこれは安全ですが、ネストされたリストでは内部オブジェクトは依然として共有されます。

python
a = [1, 2, 3]
b = a            # bはコピーではない; 同じリストを指している

b.append(4)
print(a)         # [1, 2, 3, 4]  (変更された: aとbは同じリスト)
python
b = a.copy()    # 独立したコピー
b = list(a)     # 同じ結果
b = a[:]        # これも同じ

# ネストされたリストは依然として内部オブジェクトを共有する:
matrix = [[1, 2], [3, 4]]
copy   = matrix.copy()

copy[0].append(99)
print(matrix)   # [[1, 2, 99], [3, 4]]  (内部リストは共有されていた)

完全な独立性が必要なネスト構造の場合、各内部リストを手動でコピーするか、標準ライブラリの copy.deepcopy() を使用してください。モジュール の章で扱います。

その他のメソッド

メソッド動作
.append(item)末尾に追加
.insert(i, item)位置 i に挿入
.extend(iterable)イテラブルからすべての項目を追加
.remove(value)値の最初の出現を削除
.pop(i)位置 i の項目を削除して返す (デフォルト: 最後)
.clear()すべての項目を削除
.index(value)最初の出現の位置
.count(value)出現回数
.sort()その場でソート
.reverse()その場で反転
.copy()シャローコピーを返す

実践

スコアトラッカーの構築: 結果を追加し、ソートし、サマリを出力します。

python
scores = []

scores.append(87)
scores.append(54)
scores.append(92)
scores.append(67)
scores.append(45)

scores.sort(reverse=True)

print(f"順位付けされたスコア: {scores}")
print(f"最高: {scores[0]}")
print(f"最低:  {scores[-1]}")
print(f"平均: {sum(scores) / len(scores):.1f}")
print(f"トップ3:   {scores[:3]}")

名前とスコアの2つの並列リスト: トップパフォーマーを見つけて、順位付けされた結果を出力します。

python
names  = ["さくら", "ひろし", "ゆうこ", "たかし"]
scores = [87, 74, 92, 55]

best_score  = max(scores)
best_index  = scores.index(best_score)
best_player = names[best_index]

print(f"トッププレイヤー: {best_player} ({best_score})")
print(f"平均:    {sum(scores) / len(scores):.1f}")

ranked = sorted(scores, reverse=True)
print(f"分布 (順位付け): {ranked}")

for i in range(len(ranked)):
    print(f"  順位 {i + 1}: {ranked[i]}")

エイリアシングとコピーの違い、およびネストされたリストのシャローコピーとディープコピーの違いを示します。

python
# エイリアシング: bはコピーではない
a = [1, 2, 3]
b = a
b.append(4)
print(a)    # [1, 2, 3, 4]  (同じオブジェクト)

# シャローコピー: 外部リストは独立、内部リストは共有
matrix    = [[1, 2, 3], [4, 5, 6]]
shallow   = matrix.copy()
shallow[0].append(99)
print(matrix)    # [[1, 2, 3, 99], [4, 5, 6]]  (内部リストは共有)

# forループによる手動ディープコピー (インポート不要)
matrix    = [[1, 2, 3], [4, 5, 6]]
deep_copy = []
for row in matrix:
    deep_copy.append(row[:])    # 各内部リストを明示的にコピー

deep_copy[0].append(99)
print(matrix)    # [[1, 2, 3], [4, 5, 6]]  (変更なし)