Python | リスト内包表記でスマートにリストを再構築する | 初心者向け

リスト

こんにちは。Djangoを少し調べみて、Laravelで作っいるWebアプリをPythonで書き直せたら楽しいかもしれないなぁ・・・でもコスパ悪そうと考えている小幡です。

Pythonは動的型付け言語ということもあり、Javaよりもプログラミング初心者が入門しやすい言語だと感じますが、その入門しやすさゆえに、時々見たこともないようなコードに出くわすとちょっと怯んでしまうこともあるかと思います。

その知らないと二度見してしまうコードの1つがリスト内包表記かと思いますので、今回はこちらをメインテーマにコードの解説をしていきます。

既存のリストから特定の要素だけのリストを作る

リスト内包表記という用語を使ってしまうと、何か複雑なことをするように見えますが、1つひとつやりたい事を分解していけば、難しい事はありません。基本に忠実にゆっくり見ていきましょう。

今回は以下のようなタスク(作業・やりたい事)があると仮定します。

やりたい事:全ユーザリストから、先頭が’jp’の要素のリストを作る

このやりたい事をリスト内包表記で記述すると、結果的に以下のようになります。
user_listの先頭2文字は国名コードを表しています。’jp’であれば日本のユーザ、’us’であればアメリカのユーザです。ちなみに国名コードの’ch’は中国ではなく、スイスで、中国は’cn’です。

def main():
    # 全ユーザリスト
    user_list = [
        'jp-1111-1111',
        'us-2222-2222',
        'ch-3333-3333',
        'jp-4444-4444',
    ]

    # jpユーザリスト
    jp_user_list = [
        user
        for user in user_list
        if user[:2] == 'jp'
    ]

    print(jp_user_list)

if __name__ == '__main__':
    main()

以上のコードをpython3.9のwindows環境(PowerShell)で実行します。(mac環境であればターミナルで’python3 sample_comprehension.py’を実行)

python sample_comprehension.py

実行結果は以下の通りです。

['jp-1111-1111', 'jp-4444-4444']

最初にやりたい事で明示した通り、「先頭が’jp’の要素のリストを作る」ということが出来ています。

今回のメインテーマであるリスト内包表記は10行目から15行目ですが、念のため全てのコードの解説をしてみたいと思います。1つでもわからないところがあると、Pythonの場合はなんとなく動いているからよし、という状態になってしまいます。

この「なんとなく動いているからよし」というのは、どんな仕事でもあまり陥りたくない状況です。もしもデッドラインが近づいておりクライアントからも速さ重視という指示が出されている稀な環境以外では、わからないところがないコーディングができるようにしていきたいものです。

コードの詳細解説

それでは上で記したコードの詳細を見ていきます。

def main():

まずは1行目ですが、これはmainという関数を引数なしで定義しています。現実的にはmainを定義する場面はほとんどないと思いますが、今回は1つのコードを作成し実行する流れも紹介するためにあえてmainという関数を定義しています。

全ユーザリストを作成する

    # 全ユーザリスト
    user_list = [
        'jp-1111-1111',
        'us-2222-2222',
        'ch-3333-3333',
        'jp-4444-4444',
    ]

次に「全ユーザリスト」を作ります。user_listに4つの要素を持つリスト型を代入している形になっています。

最初に空のリストを持つuser_listを用意して、後で要素を追加する方法でも良いですが、まとめることが可能で可読性も低くならない場合は、1つにまとめています。

ここで重要なのは、最後の要素’jp-4444-4444′,の末尾にカンマを付けていることです。これは付けても付けなくても正常に動作します。
付けなくても良いですし、JSONや他の言語ではエラーか挙動が変わってしまう恐れがありますが、Pythonの場合は末尾のカンマを付ける方が良いと考えて居ます。

それは、Gitなどでコードを管理する場合、新しい要素を追加する際に力を発揮するからです。カンマが無い場合に要素を追加するとカンマの行と追加した要素の行がdiffで表示されてしまうことになります。つまり2行変更されたという結果が表示されてしまうことになります。

対してカンマを予め付けておくと、要素を追加した行の1行だけがdiffで表示されることになるのでレビューするのも1行なので、「要素を1行追加した」というのが理解しやすいと思います。

もちろんこれらの考えは、プロジェクトや個人によって考え方が違うと思いますので、共同でコーディングする場合はその場の人と話合うか規約を守ることが必要だと思います。

リスト内包表記の使用、要素末のカンマの付与は、規約や全体に合わせることが1番大切だと思います。

jpユーザリストを作成する

次はいよいよリスト内包表記です。

    # jpユーザリスト
    jp_user_list = [
        user
        for user in user_list
        if user[:2] == 'jp'
    ]

リスト内包表記は、for文やif文をリストの中に表記しているというイメージで見ると理解し易いかと思います。

[]のリスト内では以下の様なイメージの処理が行われています。行の順番に沿って説明します。

  1. user : 結果としてuserをリストに追加します。
  2. for user in user_list : 単純なfor文と同様に既存のuser_listから任意の変酢名で要素を1つ取り出します(変数名userは任意で新しく作成するので、何でも構いません。)
  3. if user[:2] == ‘jp’ : 単純なif文と同様ですが、最初にに上の2で取り出したuserを[:2]で、要素の先頭2文字分をスライスでさらに取り出しています。その後取り出した2文字が’jp’と同等かを比較しており、真(True)の場合に上の1が処理されます。

リスト内包表記をしようする際、コード行が削減できるというメリットにだけ目が向いてしまい、可読性が良いとは言いづらい以下のようなコードになることがあります。確かにこの複雑なコードが1行で書けるのは魅力的かもしれませんが、時間が経った後に読み返してみると、何をしているのかよくわからないということになりかねません。

jp_user_list = [user for user in user_list if user[:2] == 'jp']

慣れるまでは無理に一行でまとめる必要はないかと思います。

また、今回は全ユーザリストをuser_listという変数に代入しましたが、リスト型の場合はusersという変数のように複数形にする’s’を付ける変数が使用されることがよくあります。

この場合だとusersからuserを取り出すので、コード量を少し削減することができますが、少し可読性が悪くなると感じます。慣れの問題かもしれませんが、’s’を見逃して複数形なのか単数形なのかでコーディングを間違ってしまうようであれば、user_listのような明示的な変数名を使用しバグがないコーディングを目指した方が良いと思います。

usersを使用した場合のfor文は以下です。

for user in users

ちなみにリスト内包表記は1行で記述できうるという特性から最後にコロンが必要ありません。一般的なfor文とif文は末尾にコロンが必要なので紛らわしいかもしれませんが、重要です。

print関数で標準出力する

    print(jp_user_list)

main関数の行末にprint関数でリスト内包表記で作成したjp_user_listを結果として標準出力しています。これによりコンソール画面に先頭文字列が’jp’で再構築されたリストが表示されます。

main関数を呼び出す

最後に19行目と20行目です。

if __name__ == '__main__':
    main()

これは、コマンドラインから直接この.pyファイルが実行されたときにmain関数を実行するというif文です。
__name__というのは、そのファイルがモジュールとして他の場所でimportされた際、モジュール名が格納されるもので、今まで説明してきたコードをまとめたファイルの名前がsample.pyだった場合、他のファイルでimportされると、__name__の中身は’sample’になります。

つまりimportされた場合はif文がFalseになりmain関数は実行されませんので、何も処理されません。

importされた場合はFalse。コマンドラインから実行された場合はTrue。

これがコマンドラインから実行された場合には__name__の中身がコマンドラインから実行したことを意味する’__main__’になるという仕組みです。

リスト内包表記を使わない場合

これまでリスト内包表記を使用した、既存のリストから特定の要素を取り出したリストを作成するという流れを見てきたわけですが、プログラミングを始めてまだ日が浅い方には難しく感じられたかもしれません。

for文やif文はわかるけれど、リスト内包表記がどうしてもわからない・・・という人は無理にリスト内包表記を使用しなくても構わないと思います。

また、先ほども書きましたが、規則や全体でリスト内包表記を使用していない場合や禁止している場合は、そちらを優先するべきだと思います。

なので、これまで説明してきたコードをリスト内包表記を使用しない例を記します。

jp_user_list = []
for user in user_list:
    if user[:2] == 'jp':
        jp_user_list.append(user)

こちらの処理でも同様の結果が得られます。

やっていることは同じですが、最後にリストに追加するappend関数を使用しています。

こちらの場合でもメリットを感じることがあります。それはVSCodeでデバッグをしている場合ですが、1つひとつの処理を実行していき変数の中身を見て取ることができます。

上の例で言うと、user_listから取り出した任意の変数userが現在どんな状態になっているかを取り出して確認することができます。

以上でリスト内包表記の説明は終わりです。

まとめ

  • リスト内包表記は単純なfor文とif文の集合と見ればわかりやすい
  • 規則や全体に合わせることが重要
  • 1行で表現されていて理解できな場合は、条件式で改行している

初めてリスト内包表記に出くわしたときは、その構造を表す単語すらわからず混乱することもあるかと思いますが、1つひとつを丁寧に解読していけば理解できるはずです。

改めてコードの解説をしてみると、様々な技術が使用されていてやっぱりPythonは面白いなぁと感じました。

皆さんのコーディングライフがより良くなることを祈ります。

この記事を書いた人

小幡 知弘

1990年茨城県神栖市生まれ
2013年大阪芸術大学卒業
Python×Webエンジニア