Pythonでデータを処理するとき、「リストの各要素に同じ処理を適用したい」という場面は非常に多いです。
このとき、書き方は主に3通りあります。
| 方法 | 特徴 | 一言で言うと |
|---|---|---|
for文 |
最も基本的で直感的 | 「一つずつ順番に処理する」 |
| リスト内包表記 | Pythonらしい簡潔な書き方 | 「一行でリストを作る」 |
map() |
関数型プログラミングのスタイル |
今回は、それぞれの書き方の違いと、実際の速度を比較してみましょう。
Contents
for文:最も基本的なループ処理
for文とは?
for文は、リストなどの「繰り返し可能なオブジェクト(イテラブル)」の要素を、一つずつ順番に取り出して処理するための構文です。
「イテラブル」という言葉が出てきましたが、簡単に言えば「for文で回せるもの」のことです。リスト、タプル、文字列、辞書などがイテラブルに該当します。
基本的な動きを確認する
まずは、for文がどのように動くのか確認してみましょう。
以下のコードでは、リストの各要素を2倍にした新しいリストを作ります。
# 元のリスト(これを加工したい)
numbers = [1, 2, 3, 4, 5]
# 結果を格納するための空のリストを用意
doubled = []
# リストの各要素を順番に処理
for number in numbers:
doubled.append(number * 2) # 2倍にして追加
print(doubled)
以下、実行結果です。
[2, 4, 6, 8, 10]
元のリストの各要素が2倍になっていることが確認できます。
処理の流れを可視化する
for文の内部で何が起きているのか、もう少し詳しく見てみましょう。
以下のコードでは、各ステップで何が行われているかを表示します。
numbers = [1, 2, 3, 4, 5]
doubled = []
for i, number in enumerate(numbers):
result = number * 2
doubled.append(result)
print(f"ステップ{i+1}: {number}×2={result} → {doubled}")
以下、実行結果です。
ステップ1: 1×2=2 → [2] ステップ2: 2×2=4 → [2, 4] ステップ3: 3×2=6 → [2, 4, 6] ステップ4: 4×2=8 → [2, 4, 6, 8] ステップ5: 5×2=10 → [2, 4, 6, 8, 10]
1ステップずつ処理が進む様子が見えます。
for文のメリット・デメリット
for文のメリットは、最も直感的で理解しやすいこと、複雑な処理も書きやすいこと、デバッグがしやすいことです。
一方、デメリットとしては、コードが長くなりがち(最低3行必要)で、「空リスト用意→append」というパターンを毎回書く必要があることが挙げられます。
リスト内包表記:Pythonらしい簡潔な書き方
リスト内包表記とは?
リスト内包表記(List Comprehension)は、リストを生成するためのPython独自の簡潔な書き方です。
for文で3行以上かかる処理を、たった1行で書けるのが特徴です。
基本構文を理解する
リスト内包表記の基本構文は [式 for 変数 in イテラブル] です。
これを日本語で読むと、「イテラブルの各要素を変数に入れて、式を評価した結果のリストを作る」となります。
実際にfor文と同じ処理を書いてみましょう。
numbers = [1, 2, 3, 4, 5] # リスト内包表記 doubled = [number * 2 for number in numbers] print(doubled)
以下、実行結果です。
[2, 4, 6, 8, 10]
for文と同じ結果が、たった1行で得られました。
この簡潔さがリスト内包表記の最大の魅力です。
条件付きリスト内包表記
リスト内包表記には条件(if)を追加することもできます。
これにより、特定の条件を満たす要素だけを処理できます。以下のコードでは、偶数だけを2倍にしています。
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 偶数だけを2倍にする(if条件を追加) doubled_evens = [n * 2 for n in numbers if n % 2 == 0] print(doubled_evens)
以下、実行結果です。
[4, 8, 12, 16, 20]
n % 2 == 0 は「2で割った余りが0」、つまり偶数を意味します。
このように、フィルタリングと変換を1行で書けるのがリスト内包表記の強力な点です。
データ分析で「特定の条件のデータだけを抽出して加工する」といった場面で活躍します。
map():関数を全要素に適用する
map()とは?
map() は、リストなどの全要素に対して、指定した関数を適用するための組み込み関数です。
「関数型プログラミング」というプログラミングスタイルに由来する機能で、「データの変換」を表現するのに適しています。
基本的な使い方
map()の基本構文は map(関数, イテラブル)です。
まず変換処理を関数として定義し、それをmap()に渡します。
numbers = [1, 2, 3, 4, 5]
# 2倍にする関数を定義
def double(x):
return x * 2
# map()で全要素に関数を適用
doubled = list(map(double, numbers))
print(doubled)
以下、実行結果です。
[2, 4, 6, 8, 10]
重要なポイントは、map()はそのままでは「mapオブジェクト」を返すため、list() で囲んでリストに変換する必要があることです。
lambda式との組み合わせ
「2倍にするだけの関数」をわざわざ定義するのは面倒ですよね。
そこで活躍するのが lambda(ラムダ)式 です。
lambda式は「名前のない小さな関数」を1行で書くための構文で、lambda 引数: 式 という形式で書きます。
numbers = [1, 2, 3, 4, 5] # lambda式を使うと関数定義が不要 doubled = list(map(lambda x: x * 2, numbers)) print(doubled)
以下、実行結果です。
[2, 4, 6, 8, 10]
lambda x: x * 2 は「引数xを受け取って、x * 2を返す」という小さな関数を表しています。
ただし、lambda式を多用するとコードが読みにくくなることもあるので、使いどころを見極めることが大切です。
組み込み関数との組み合わせ
map()は、Pythonに最初から用意されている「組み込み関数」と組み合わせると特に便利です。
以下は、文字列のリストを数値のリストに変換する例です。
# 文字列になっている数字 str_numbers = ["1", "2", "3", "4", "5"] # int関数を全要素に適用して整数に変換 int_numbers = list(map(int, str_numbers)) print(int_numbers) print(type(int_numbers[0])) # 型を確認
以下、実行結果です。
[1, 2, 3, 4, 5] <class 'int'>
int は文字列を整数に変換する組み込み関数です。
lambda式を書かずに関数名だけを渡せるので、非常にシンプルです。
速度比較
計測の準備
ここからが本題です。3つの方法で同じ処理を行い、実際の処理時間を比較します。
まず、計測条件を確認しましょう。
# 計測条件 DATA_SIZE = 100000000 # 1億個のデータ REPEAT = 10 # 10回繰り返す # 処理対象のデータ data = list(range(DATA_SIZE))
1億個という大きなデータを使うことで、3つの方法の速度差が明確に現れます。
今回は、各要素を2乗するという処理を実施します。
計測するためのtimeitモジュールの使い方
Pythonには処理時間を計測するための専用モジュール timeit があります。手動で時間を計測するよりも正確で、使い方も簡単です。
import timeit
# 計測条件
DATA_SIZE = 100000000 # 1億個のデータ
REPEAT = 10 # 10回繰り返す
# 計測対象のコードを文字列で渡す
# setup: 事前準備(計測対象外)
# number: 実行回数
time_for = timeit.timeit(
'result = []\nfor x in data: result.append(x**2)', # 計測対象のコード
setup = f'data = list(range({DATA_SIZE}))', # 事前準備(計測対象外)
number=REPEAT # 実行回数
)
# 計測結果を表示
print(f"for文: {time_for:.3f}秒")
以下、実行結果です。
for文: 55.106秒
3つの方法を一度に比較する
それでは、3つの方法すべてを計測してみましょう。以下のコードをそのまま実行できます。
import timeit
# 計測条件
DATA_SIZE = 100000000 # 1億個のデータ
REPEAT = 10 # 10回繰り返す
# 共通の事前準備(計測対象外)
setup = f'data = list(range({DATA_SIZE}))'
# for文
t_for = timeit.timeit(
'r = []\nfor x in data: r.append(x**2)',
setup=setup,
number=REPEAT
)
# リスト内包表記
t_comp = timeit.timeit(
'[x**2 for x in data]',
setup=setup,
number=REPEAT
)
# map()
t_map = timeit.timeit(
'list(map(lambda x: x**2, data))',
setup=setup,
number=REPEAT
)
print(f"for文: \t{t_for:.3f}秒")
print(f"リスト内包表記:\t{t_comp:.3f}秒")
print(f"map(): \t{t_map:.3f}秒")
以下、実行結果です。
for文: 55.226秒 リスト内包表記: 52.700秒 map(): 78.657秒
この結果から、リスト内包表記が最も速いことがわかります。
まとめ
for文は最も直感的で柔軟ですが、コードは長くなりがちです。
map()は関数適用を明確に表現できますが、可読性や速度面で注意が必要です。
速度と読みやすさの両立を考えると、リスト内包表記が最も実用的と言えるでしょう。

