19
20

More than 3 years have passed since last update.

8つのレベルでPythonのリスト内包表記を使ってみる

Posted at

本記事は、Yang Zhou氏による「8 Levels of Using List Comprehension in Python」(2020年10月14日公開)の和訳を、著者の許可を得て掲載しているものです。

8つのレベルでPythonのリスト内包表記を使ってみる

Image for post

Photo by Jason Cooper on Unsplash

はじめに

リスト内包表記は、とてもPythonらしい技術であり、コードをとても洗練されたものにできる技術です。ただし構文は、特に初心者や他言語経験者のプログラマには少し分かりにくいです。リスト内包表記に関する資料を沢山読みましたが、正直なところ、これがどれほど強力で美しいか完璧に説明しているものはありませんでした。そのような訳でこの記事があります😊

この記事では、初歩から奥義まで、8つのレベルで内包表記を使用する方法を紹介します。
全てのレベルを理解すれば、リスト内包表記を習得するのは簡単です。

レベル0:リスト内包表記の書式を知る

まず、基本的な構文を理解する必要があります。

リスト内包表記は、次のような書式を使用します。

my_list=[ 式 for 変数 in イテラブル (if条件) ]

とてもすっきりしていてシンプル、2つの角括弧の中に3つの主要な要素を入れるだけです。

  • イテラブルを反復処理するためのforループ
  • 変数を扱うための式
  • 任意のif条件

次に、この簡単な書式を使って、工夫を凝らしたプログラムを書く方法を見てみましょう。

レベル1:ただforループを置き換える

直感的な方法は、forループを1行のコードに置き換えることです。

full_name = "Yang Zhou"
characters = [char for char in full_name]
print(full_name)
print(characters)
# Yang Zhou
# ['Y', 'a', 'n', 'g', ' ', 'Z', 'h', 'o', 'u']

example1.py

次の例と比較すると、これはすでにPythoらしく洗練されたプログラムへの第一歩です。

full_name = "Yang Zhou"
characters = []
for char in full_name:
    characters.append(char)
print(full_name)
print(characters)
# Yang Zhou
# ['Y', 'a', 'n', 'g', ' ', 'Z', 'h', 'o', 'u']

example1_forloop.py

Pythonのイテラブルはすべてリスト内包表記で使用できます。

Matrix = [[2, 1, 5],
          [5, 99, 0],
          [33, 2, 4]]
row_max = [max(row) for row in Matrix]
print(row_max)
# [5, 99, 33]

example2.py

前の例のように、行列の各行の最大値を1行のコードで取得できます。

レベル2:if条件を賢く使う

if条件は任意です。うまく使えば便利です。

Genius = ["Yang", "Tom", "Jerry", "Jack", "tom", "yang"]
L1 = [name for name in Genius if name.startswith('Y')]
L2 = [name for name in Genius if name.startswith('Y') or len(name) < 4]
L3 = [name for name in Genius if len(name) < 4 and name.islower()]
print(L1, L2, L3)
# ['Yang'] ['Yang', 'Tom', 'tom'] ['tom']

example3.py

レベル3:より複雑な式を使う

前の例では、リストを作成するために変数を取得しただけです。実は、変数に対してより複雑な式を適用できます。

Genius = ["Jerry", "Jack", "tom", "yang"]
L1 = [name.capitalize() for name in Genius]
print(L1)
# ['Jerry', 'Jack', 'Tom', 'Yang']

example4.py

if...else...文も含めて。

Genius = ["Jerry", "Jack", "tom", "yang"]
L1 = [name if name.startswith('y') else 'Not Genius' for name in Genius]
print(L1)
# ['Not Genius', 'Not Genius', 'Not Genius', 'yang']

example5.py

:リスト内包表記の書式を本当に理解していないと、混乱し得る問題が1つあります。

式のif...else...文は三項演算子とも呼ばれ、リスト内包表記の書式の最後にある、任意のif条件とは異なります。書式を確認しましょう。

my_list=[ 式 for 変数 in イテラブル (if条件) ]

書式のように、最後のif条件はリスト内包表記の要素の1つです。この後にelse文を追加することは、構文でサポートされていません。

式の部分は、Pythonの式の構文に従えば、どんなものでも可能です。ifを使用する場合は三項演算子の構文なので、elseも必要です。

a = 1
b = 2 if a>0 # SyntaxError: invalid syntax

b = 2 if a > 0 else -1
# b==2,ternary conditional operator works

example6.py

レベル4:ネストされたforループを使って、ネストされたイテラブルを処理する

1つのリスト内包表記は、1つのforループだけでなく、ネストされたforループを置き換えることができます。

Genius = ["Jerry", "Jack", "tom", "yang"]
L1 = [char for name in Genius for char in name]
print(L1)
# ['J', 'e', 'r', 'r', 'y', 'J', 'a', 'c', 'k', 't', 'o', 'm', 'y', 'a', 'n', 'g']

example7.py

前の例は次の例と同等です。

Genius = ["Jerry", "Jack", "tom", "yang"]
L1 = []
for name in Genius:
    for char in name:
        L1.append(char)
print(L1)

example7_forloop.py

どちらが良いですか? 答えは一目瞭然です😃

もちろん、ネストされたforループを更に追加することもできますが、それは良くありません。読みやすくするには、1つに3つ以上のforループを使用しないのがベストです。

さらに、forループの後に任意のif条件を追加できます。

Genius = ["Jerry", "Jack", "tom", "yang"]
L1 = [char for name in Genius if len(name) < 4 for char in name]
print(L1)
# ['t', 'o', 'm']

example8.py

レベル5:高階関数を避けて、読みやすくする

Pythonにはmap()filter()などの高階関数があります。高階関数の代わりにリスト内包表記をいつも使う習慣をつけると良いでしょう。他の人がプログラムを読みやすくなるからです。Pythonの作者でさえ、自分の記事でこの方法を推奨しています。

map()は常に置き換えられます。

L = map(func, iterable)
# can be replaced to:
L = [func(a) for a in iterable]

filter()も置き換えられます。

L = filter(condition_func, iterable)
# can be converted to
L = [a for a in iterable if condition]

次は、リスト(L1L2)を異なる方法で実装しても、同じ結果になる例です。

Genius = ["Jerry", "Jack", "tom", "yang"]
L1 = filter(lambda a: len(a) < 4, Genius)
print(list(L1))
# ['tom']
L2 = [a for a in Genius if len(a) < 4]
print(L2)
# ['tom']

example9.py

レベル6:ジェネレータ式を使って、メモリコストを削減する

角括弧を丸括弧に変換すると、リスト内包表記はジェネレータ式になります。

ジェネレータが遅延評価を適用するため、ジェネレータ式は完全なリストの生成を回避することで、メモリコストを削減できます。

large_list = [x for x in range(1_000_000)]
large_list_g = (x for x in range(1_000_000))
print(large_list.__sizeof__())
print(large_list_g.__sizeof__())
# 8697440
# 96

example9.py

レベル7:リスト内包表記の背景にある哲学を理解する

リスト内包表記を使う直感的な理由は、コードをよりすっきりと洗練させるためです。さらに、関数型プログラミングパラダイムの良い実践です。関数型プログラミングの哲学の1つに制御フローの回避があります。リスト内包表記は、プログラマの焦点を制御フローから、データ収集そのものに移すことができます。つまり、forループがどのように機能するか考えることから、リストとは何かを考えることに、精神的にシフトするのです。プログラム全体のロジックをより簡単に考えることができます。

まとめ

リスト内包表記は、Pythonプログラムがいかに洗練されているかが分かる典型例です。構文に精通し、シナリオを使用すれば、Pythonプログラミングスキルは新しい領域に入るでしょう。

お読み頂きありがとうございました!

翻訳協力

Original Author: Yang Zhou
Original Article: 8 Levels of Using List Comprehension in Python
Thank you for letting us share your knowledge!

この記事は以下の方々のご協力により公開する事ができました。改めて感謝致します。
選定担当: @gracen
翻訳担当: @gracen
監査担当: -
公開担当: @gracen

ご意見・ご感想をお待ちしております

今回の記事は、いかがだったでしょうか?
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。
頂いたお声は、今後の記事の質向上に役立たせて頂きますので、お気軽に
コメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
皆様のメッセージをお待ちしております。

19
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
20