Pythonでプログラミングを行う際、データを格納する箱として「リスト」を準備する作業は避けて通れません。
効率的なコードを書くためには、用途に応じた最適な初期化方法を選択することが重要です。
本記事では、基本的な空リストの作成から、多次元リストを扱う際の注意点まで、実務で即戦力となるベストプラクティスを詳しく紹介します。
空のリストを初期化する基本の方法
Pythonで最もシンプルにリストを作成する方法は、中身が何もない「空の状態」で初期化することです。
これには主に2つの記述方法が存在し、それぞれに特徴があります。
リテラル記法 [] を使用する
最も一般的で推奨される方法は、ブラケット [] を使用したリテラル記法です。
この書き方は非常にシンプルであり、視覚的にもリストであることが直感的に理解しやすいため、多くの開発現場で採用されています。
# 空のリストをリテラルで初期化
empty_list = []
print(empty_list)
[]
また、内部的な処理速度においても、後述するコンストラクタ形式よりわずかに高速であるというメリットがあります。
コンストラクタ list() を使用する
もう一つの方法は、list() 関数を呼び出してインスタンスを生成する方法です。
明示的に「リスト型を作成している」ことを示したい場合や、他のデータ型(タプルや集合など)から変換する際に利用されます。
# listコンストラクタによる初期化
empty_list_factory = list()
print(empty_list_factory)
[]
通常の空リスト作成においては [] を使用し、既存の反復可能オブジェクトをリスト化する場合に list() を使用するのが一般的な使い分けです。
特定の要素数や値でリストを初期化する方法
あらかじめ決まった長さのリストを作成し、すべての要素を特定の値(0やNoneなど)で埋めたいケースは多々あります。
ここでは、Pythonらしい効率的な書き方を見ていきましょう。
乗算演算子 * を活用した初期化
同じ値を持つ要素を複数並べたい場合、乗算演算子 * を使うのが最も効率的です。
# 0で初期化された長さ5のリストを作成
zeros = [0] * 5
print(zeros)
# 特定の文字列で初期化
labels = ["pending"] * 3
print(labels)
[0, 0, 0, 0, 0]
["pending", "pending", "pending"]
この方法は非常に高速ですが、リストの中にリストを入れる多次元配列の初期化には向かないという重要な落とし穴があります(詳細は後述します)。
連続した数値で初期化する range() 関数
「0から9まで」といった連続する数値を持つリストを作成したい場合は、range() 関数と list() を組み合わせます。
# 0から9までの数値で初期化
numbers = list(range(10))
print(numbers)
# 10から20までの2刻みの数値で初期化
even_numbers = list(range(10, 21, 2))
print(even_numbers)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[10, 12, 14, 16, 18, 20]
ループ処理のカウンタとして利用するだけでなく、データのインデックスリストを生成する際にも便利です。
リスト内包表記による高度な初期化
複雑な条件や計算を伴うリストを作成する場合、リスト内包表記が非常に強力な武器となります。
内包表記を使うことで、forループを記述するよりも簡潔かつ高速にリストを構築できます。
# 1から5までの2乗を要素に持つリスト
squares = [i**2 for i in range(1, 6)]
print(squares)
# 偶数のみを抽出して初期化
evens = [i for i in range(10) if i % 2 == 0]
print(evens)
[1, 4, 9, 16, 25]
[0, 2, 4, 6, 8]
可読性を損なわない範囲であれば、内包表記を積極的に活用することがPythonicなコードへの近道です。
多次元リスト(二次元配列)を初期化する際の注意点
行列のような多次元構造を初期化する際には、初心者だけでなく中級者も陥りやすい罠が存在します。
乗算演算子による多次元リストの失敗例
以下のコードは、一見すると3行3列の「0」で埋められた行列を作成しているように見えます。
# 危険な初期化方法
matrix = [[0] * 3] * 3
print("初期状態:", matrix)
# 1箇所だけ書き換えたつもりが...
matrix[0][0] = 9
print("変更後:", matrix)
初期状態: [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
変更後: [[9, 0, 0], [9, 0, 0], [9, 0, 0]]
実行結果を見ると、最初の要素だけを書き換えたつもりが、すべての行の先頭が「9」に変わってしまっています。
これは、外側の * 3 が同じリストオブジェクトへの参照をコピーしてしまっている、いわゆる浅いコピー(shallow copy)が原因です。
正しい多次元リストの初期化
多次元リストを安全に初期化するためには、各行を独立したオブジェクトとして生成するリスト内包表記を使用する必要があります。
# 安全な多次元リストの初期化
correct_matrix = [[0] * 3 for _ in range(3)]
correct_matrix[0][0] = 9
print(correct_matrix)
[[9, 0, 0], [0, 0, 0], [0, 0, 0]]
この書き方であれば、各行が異なるメモリ領域に割り当てられるため、意図しない値の書き換えを防ぐことができます。
初期化手法の比較とパフォーマンス
状況に応じて適切な方法を選択できるよう、各初期化手法の特徴を以下の表にまとめました。
| 手法 | 用途 | パフォーマンス | 注意点 |
|---|---|---|---|
[] | 空リストの作成 | 最速 | 特になし |
[v] * n | 同一値での埋め込み | 非常に高速 | ミュータブルな要素(リスト等)に不向き |
list(range()) | 連続数値の生成 | 高速 | 数値以外の生成には不向き |
| リスト内包表記 | 動的な生成・加工 | 標準的 | 複雑すぎると可読性が低下する |
基本的には 「シンプルさ」と「安全性」の両立 を目指し、基本の初期化には * 演算子を、構造化データには内包表記を選択するのがベストです。
まとめ
Pythonにおけるリストの初期化は、単純な [] による作成から、内包表記を駆使した高度な生成まで多岐にわたります。
特に * 演算子を用いた多次元リストの初期化における「参照のコピー」問題は、バグの原因になりやすいため注意が必要です。
状況に応じて最適な書き方を使い分けることで、バグが少なく、かつパフォーマンスに優れた美しいコードを書くことができるようになります。
今回紹介したパターンを参考に、日々のコーディングにおいて最も効率的なリスト初期化を実践してみてください。
