これまでfor文(https://augmented-vr.com/using-for-loops-in-python/)やリスト(https://augmented-vr.com/use-lists-in-python/)の使い方を学習してきました。リストにデータを追加するときは、空のリストを用意してfor文とappend()を使うのが基本でした。今回は、その処理を1行で、しかも高速に書くことができるPython特有の書き方「内包表記(ないほうひょうき)」について解説します。

内包表記を使用する

Pythonで内包表記を使うには、角括弧「[]」の中に、実行したい「式」、スペース、「for 変数 in リストなど」を書きます。なので例として内包表記は、

変数名 = [式 for 変数 in リストなど]

の形式で記述します。

内包表記では次のような順番で処理が行われています。

  1. 要素の取り出し: まず右側のfor 変数 in リストなど が実行され、元のデータから1つずつ値が変数に取り出されます。
  2. 式の計算(評価): その取り出した変数を使って、一番左側に書かれたが計算されます。
  3. リストの構築: 計算された結果が、全体を囲んでいる角括弧[]の働きによって、新しいリストの要素として自動的に追加されていきます。

なぜ内包表記を使うと処理が速くなるのか

通常のfor文でappend()を何度も使ってリストにデータを追加すると、そのたびにPythonが「関数を呼び出す」という処理を行うため、わずかに時間がかかってしまいます。しかし、内包表記はPythonの内部で「新しいリストを一気に作り上げる」ための専用の最適化が施されているため、関数呼び出しの無駄がなくなり、圧倒的に高速に動作するという大きなメリットがあります。

if文を組み合わせる(条件付き内包表記)

内包表記の後ろにif文をつなげることで、「特定の条件を満たす要素だけ」を取り出して新しいリストを作ることができます。なので例として条件付き内包表記は、

変数名 = [式 for 変数 in リストなど if 条件式]

の形式で記述します。

if文が追加されると、次のような処理になります。

  1. forで元のリストから要素を1つ取り出します。
  2. 一番右側のif 条件式 で、その要素が条件を満たしているか(Trueか)を判定します。
  3. もし条件がTrue(真)だった場合のみ、一番左のにデータが渡されて計算されます。もしFalse(偽)だった場合は無視され、次の要素の取り出しに進みます。
  4. 計算された結果だけが、新しいリストに追加されます。

では、実際にやってみましょう。 Google Colaboratory (Colab)を開いて新しいノートブックを作成し、従来のfor文と内包表記を比較するプログラムを書いてみます。今回は「1から5までの数字のうち、偶数だけを2倍にして新しいリストを作る」という処理を行います。

# 元になる数字のリスト
numbers = [1, 2, 3, 4, 5]

# [従来の書き方]空のリストを用意し、for文とappendを使う
old_list = []
for num in numbers:
  if num % 2 == 0:
    old_list.append(num * 2)

print("従来のfor文:", old_list)

# [内包表記の書き方]1行で同じ処理を書く
new_list = [num * 2 for num in numbers if num % 2 == 0]

print("内包表記:", new_list)

とセルに入力して実行してみましょう。すると結果は

従来のfor文: [4, 8]
内包表記: [4, 8]

のように、同じ結果のリストが表示されます。

プログラムの解説

  • 元のリストの準備(2行目): 変数numbers に1から5までの数値が入ったリストを用意しています。
  • 従来の書き方(5~8行目): まず空のリストold_listを作り、for文でnumbersから数値を1つずつnumに取り出します。if num % 2 == 0 で偶数かどうかを判定し、偶数の場合だけ num * 2 の計算結果を append() でリストに追加しています。合計4行のコードが必要です。
  • 内包表記の書き方(13行目): [num * 2 for num in numbers if num % 2 == 0] と記述しています。
    • 内部では、まず for num in numbers で数値を1つ取り出します。
    • 次に if num % 2 == 0 でその数値が偶数かチェックします。
    • 偶数だった場合のみ、左側の num * 2 が計算され、自動的に新しいリスト new_list の要素として追加されます。たった1行で、しかも従来より高速に同じ処理が完了しています。

[補足]処理速度を実際に測って比較してみる

内包表記がどれくらい速いのか、実際にプログラムの「処理時間」を計測して比べてみましょう。

処理時間を計るには、Pythonに最初から用意されている「time(タイム)モジュール」を使用します。

標準ライブラリtimeモジュールについて

Pythonには、最初から用意されている基本機能のほかに、「モジュール」と呼ばれる便利な機能が詰まったものが用意されています。モジュールについては以前のrandomモジュールの記事(https://augmented-vr.com/using-pythons-random-module/)で学習しました。

timeモジュールは、その中でも「時間」に関する処理を行うための有名で標準的なライブラリの一つです。プログラムの処理にかかった時間を計測したり、現在の時刻を取得したり、意図的にプログラムの動きを数秒間一時停止させたりすることができるツールです。処理速度を検証したり、時間間隔を制御したりするプログラムなど、様々な場面で世界中で使われています。

このモジュールはPythonの「標準ライブラリ」として最初から組み込まれているため、面倒なインストール作業(pip install など)は一切不要で、すぐに使うことができます。

時間を計る仕組み

このtimeモジュールの中にあるtime.time()という機能を使うと、「現在時刻」を秒単位の数値(小数点以下を含む秒数)として取得することができます。これを利用して、ストップウォッチのように処理時間を計ります。具体的な手順は以下の通りです。

  1. 処理を始める直前にtime.time()で時刻を記録する
  2. 測りたい処理(今回はリストの作成)を実行する
  3. 処理が終わった直後に再びtime.time()で時刻を記録する
  4. 「終了時刻」から「開始時刻」を引き算すると、その処理にかかった秒数が分かる

使用するには、プログラムの先頭でimport time と記述してモジュールを読み込みます。

では、実際に100万回の計算を行って、従来のfor文と内包表記でどれくらい速度が違うのか実験してみましょう。
Colabの新しいセルに以下のコードを入力して実行してみてください。

import time

# 100万個の数字のデータを用意する
# (range(1000000)は0から999999までの数字を作る)
numbers = range(1000000)

print("--- 処理を開始します ---")

# [従来のfor文]の時間を計る
start_time1 = time.time() # 開始時刻を記録

old_list = []
for num in numbers:
  old_list.append(num * 2)

end_time1 = time.time() # 終了時刻を記録
time1 = end_time1 - start_time1 # かかった時間を計算

print(f"従来のfor文: {time1}秒")

# [内包表記]の時間を計る
start_time2 = time.time() # 開始時刻を記録

new_list = [num * 2 for num in numbers]

end_time2 = time.time() #終了時刻を記録
time2 = end_time2 - start_time2 # かかった時間を計算

print(f"内包表記: {time2}秒")

とセルに入力して実行してみましょう。すると結果は

— 処理を開始します —
従来のfor文: 0.12739777565002441秒
内包表記: 0.07737946510314941秒

のように表示されます。なお、実行するパソコンの性能やタイミングなどによって実際の秒数は変わります。

プログラムの解説(速度計測)

  • モジュールの読み込み(1行目): import time で時間を扱うためのモジュールを使えるように準備しています。
  • 時間の記録(10行目、22行目など): time.time()を実行すると、その瞬間の時刻(秒数)が取得できます。それを変数start_time1(開始時刻)やend_time1(終了時刻)に代入して記録しています。
  • 時間の計算(17、27行目): 「終了時刻 – 開始時刻」を引き算することで、その間に実行された処理(リストの作成)にかかった秒数を割り出しています。

結果を見ると、内包表記の方が速く処理が終わっていることが分かります。扱うデータが何十万、何百万と大きくなればなるほど、この速度の差がプログラム全体の性能に直結してきます。

まとめ

今回は、リスト作成の処理を短く高速に書ける「内包表記」について解説しました。

  • 内包表記とは: [式 for 変数 in リストなど] の形式で、for文とリストの追加処理を1行にまとめる書き方。
  • if文との組み合わせ: 後ろにif 条件式をつけると、条件を満たしたデータだけでリストを作ることができる。
  • メリット: コードが短く読みやすくなるだけでなく、内部処理が最適化されているため実行速度が速くなる。
  • 裏側の処理順: 「for で取り出す」「ifで判定する」「式で計算してリストに入れる」という順序で動いている。
  • 処理速度の計測: 標準ライブラリであるtimeモジュールのtime.time()を使って開始時刻と終了時刻を記録し、引き算をすることでプログラムの処理時間を正確に計ることができる。

最初は難しいと思いますが、便利な機能なので、ぜひ使ってみてください。

open-in-colab

今回はこれで終了です。
今回のサンプルを用意したので、もし必要な場合は上の「Open in Colab」と書いてあるボタンをクリックしてください。なおそのままだと編集不可なので編集をしたい場合は「ドライブにコピー」をクリックしてコピーしてください。
なおGoogle ColaboratoryについてわからなかったらGoogleColaboratoryを使ってみよう_導入編をご覧ください。

コメントを残す

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

CAPTCHA