Pythonにおいて、複数のリストを一つにまとめる「結合」は、プログラミングのあらゆる場面で頻出する基本的な操作です。
単純に2つのリストをつなげるだけでなく、処理速度やメモリ効率、コードの可読性を考慮した最適な手法の選択が求められます。
本記事では、Pythonでリストを結合するための代表的な6つの方法を、具体的なコード例とともに詳しく紹介します。
それぞれの特性を理解し、プロジェクトの要件に合わせた使い分けができるようになることを目指しましょう。
1. + 演算子を使用した結合
最も直感的で利用頻度が高いのが、+ 演算子(加算演算子)を用いた方法です。
この方法は、2つのリストを連結して「新しいリスト」を生成するのが特徴です。
# 2つのリストを定義します
list_a = [1, 2, 3]
list_b = [4, 5, 6]
# + 演算子で結合します
combined_list = list_a + list_b
# 結果を出力します
print(combined_list)
print(f"list_aのID: {id(list_a)}")
print(f"combined_listのID: {id(combined_list)}")
[1, 2, 3, 4, 5, 6]
list_aのID: 1402345678901
combined_listのID: 1402345678952
上記の実行結果からわかるように、元のリストとは異なる新しいメモリ空間にリストが作成されます。
元のリスト list_a や list_b の内容は変更されないため、破壊的な変更を避けたい場合に適しています。
ただし、非常に大きなリストを繰り返し結合する場合、毎回新しいリストを作成するため、メモリ消費量が増加する可能性がある点に注意が必要です。
2. extend() メソッドによる破壊的結合
既存のリストに別のリストの要素を追加したい場合は、extend() メソッドを使用します。
このメソッドは、呼び出し元のリストを直接書き換える(インプレース操作)ため、新しいリストを生成しません。
# 元のリストを定義します
list_x = ["apple", "banana"]
list_y = ["cherry", "date"]
# extendメソッドを実行します
list_x.extend(list_y)
# 結果を確認します
print(list_x)
['apple', 'banana', 'cherry', 'date']
extend() は引数に「イテラブル(反復可能なオブジェクト)」を受け取るため、リスト以外のタプルや集合なども結合可能です。
新しいオブジェクトを作成するコストが発生しないため、大量のデータを扱う際のパフォーマンスにおいて + 演算子よりも優れています。
元のデータが上書きされても問題ないシーンでは、積極的に extend() を活用しましょう。
3. アスタリスク (*) を用いたアンパック結合
Python 3.5以降では、アンパック演算子 * を使ってリストを結合することができます。
この方法は、複数のリストを一度に結合したり、リストの途中に別のリストを挿入したりする場合に非常に便利です。
# 複数のリストを用意します
list_1 = [10, 20]
list_2 = [30, 40]
list_3 = [50, 60]
# アンパックを利用して結合します
merged_list = [*list_1, *list_2, *list_3]
# 間に別の値を挟むことも可能です
inserted_list = [0, *list_1, 99, *list_2]
print(f"結合結果: {merged_list}")
print(f"挿入結果: {inserted_list}")
結合結果: [10, 20, 30, 40, 50, 60]
挿入結果: [0, 10, 20, 99, 30, 40]
可読性が高く、モダンなPythonの書き方として広く普及しています。
+ 演算子と同様に、新しいリストを生成する非破壊的な操作です。
複数のソースからデータを集約して新しいリストを作りたいときに、最もスマートに記述できる手法と言えるでしょう。
4. itertools.chain を活用した効率的なループ
非常に巨大なリストを複数結合してループ処理を行いたい場合、物理的に結合した新しいリストを作るのは効率的ではありません。
標準ライブラリの itertools.chain を使うと、実際に結合することなく、複数のリストを連続して処理できます。
import itertools
# 巨大なデータを想定したリスト
data_part1 = list(range(1000000))
data_part2 = list(range(1000000, 2000000))
# 物理的な結合を行わず、イテレータを作成します
chained_data = itertools.chain(data_part1, data_part2)
# 最初の数要素だけ確認します
for i, val in enumerate(chained_data):
if i > 5:
break
print(val)
0
1
2
3
4
5
itertools.chain は、各要素にアクセスする際にその都度値を参照するため、メモリをほとんど消費しません。
「結合した結果のリスト」そのものが欲しいのではなく、単に「順番に処理したいだけ」という場合に最強のパフォーマンスを発揮します。
5. スライスを用いた特定の場所への挿入・結合
Pythonのスライス機能を利用して、リストの特定の位置に別のリストを結合(代入)することも可能です。
末尾に結合するだけでなく、任意の位置を置き換える際に応用が利くテクニックです。
base_list = [1, 2, 5, 6]
add_list = [3, 4]
# インデックス2の位置にリストを挿入・結合します
base_list[2:2] = add_list
print(base_list)
[1, 2, 3, 4, 5, 6]
この手法は extend() と同様に破壊的であり、元のリストを直接操作します。
base_list[len(base_list):] = add_list と記述すれば、extend() と同じ効果を得ることもできます。
やや特殊な書き方ですが、リストの内部を柔軟に入れ替えたい上級者向けの操作です。
6. 累算代入演算子 (+=) の挙動
+= 演算子もリストの結合によく使われますが、実は + とは挙動が異なります。
リストにおいて a += b は、内部的に a.extend(b) とほぼ同等の動作をします。
list_m = [1, 2]
list_n = [3, 4]
# += を使用します
print(f"実行前のID: {id(list_m)}")
list_m += list_n
print(f"実行後のID: {id(list_m)}")
print(list_m)
実行前のID: 1402345679100
実行後のID: 1402345679100
[1, 2, 3, 4]
IDが変わっていないことからわかる通り、+= は既存のリストを更新する破壊的処理です。
list_m = list_m + list_n と書くと新しくリストが生成されますが、list_m += list_n なら上書きされるという違いを明確に意識しましょう。
各結合手法の比較一覧表
状況に応じて最適な方法を選べるよう、それぞれの特徴をまとめました。
| 手法 | 破壊性 | メモリ効率 | 主な用途 |
|---|---|---|---|
| + 演算子 | 非破壊 | 低い(新作成) | 小規模な結合、可読性優先 |
| extend() | 破壊的 | 高い | 既存リストへの追加、高速化 |
| アンパック (*) | 非破壊 | 低い(新作成) | 複数リストの集約、途中挿入 |
| itertools.chain | 非破壊 | 非常に高い | 巨大なデータのループ処理 |
| スライス代入 | 破壊的 | 高い | 特定位置への挿入 |
| += 演算子 | 破壊的 | 高い | 既存変数の簡潔な更新 |
パフォーマンスと実行速度の検証
大量のデータを扱う際、どの手法が最も速いのかは重要な関心事です。
一般的に、新しいリストを生成する + よりも、既存のメモリ領域を再利用しようとする extend() や += の方が高速です。
これは、新しいオブジェクトのメモリ確保と、全要素のコピーにかかるオーバーヘッドが + では大きくなるためです。
Python内部では、リストのサイズを拡張する際に少し多めにメモリを確保する「オーバーアロケーション」という仕組みがあります。
extend() はこの仕組みを最大限活用できるため、追加要素が多いほど効率が良くなります。
一方で、メモリ使用量を極限まで抑えたい場合は、データの実体をコピーしない itertools.chain が最適解となります。
結合時の注意点:二次元リストの挙動
リストの中にさらにリストが入っている「多次元リスト」を結合する際は注意が必要です。
これらすべての結合手法は、基本的には「浅いコピー(Shallow Copy)」です。
リスト内の「オブジェクトへの参照」がコピーされるだけなので、結合後のリストで内部のリスト要素を変更すると、元のリストにも影響が及ぶ場合があります。
このようなケースでは、copy.deepcopy() を検討するか、内包表記を利用して新しい要素として生成する必要があります。
まとめ
Pythonでリストを結合する方法は多岐にわたり、それぞれに明確なメリットとデメリットが存在します。
もっともシンプルな + 演算子は小規模なデータやコードの分かりやすさを重視する場合に適しています。
一方で、大規模なデータ処理やパフォーマンスが求められるアプリケーションでは、extend() や itertools.chain の活用が不可欠です。
「新しいリストを作りたいのか」「既存のリストを更新したいのか」という視点を常に持ち、最適なメソッドを選択しましょう。
今回紹介した6つの方法をマスターすることで、より効率的でPythonicなコードを書くことができるようになります。
