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

>本記事は、[Yang Zhou](https://yangzhou1993.medium.com/)氏による「[8 Levels of Using List Comprehension in Python](https://medium.com/techtofreedom/8-levels-of-using-list-comprehension-in-python-efc3c339a1f0)」（2020年10月14日公開）の和訳を、著者の許可を得て掲載しているものです。

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

![Image for post](https://miro.medium.com/max/814/0*cSiYxOrXxzEoX7dd)

>Photo by [Jason Cooper](https://unsplash.com/@salty_sandals?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral)

##はじめに

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

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

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

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

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

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

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

- [イテラブル](https://medium.com/techtofreedom/iterable-and-iterator-in-python-dbe7011d1ff7)を反復処理するためのforループ
- 変数を扱うための式
- 任意の`if`条件

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

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

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

```python
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](https://gist.github.com/ZhouYang1993/0b72f22d364df82621230425ec48b3e1#file-example1-py)

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

```python
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](https://gist.github.com/ZhouYang1993/198f3c46c4546efeafeb6a57e40a8591#file-example1_forloop-py)

Pythonの[イテラブル](https://medium.com/techtofreedom/iterable-and-iterator-in-python-dbe7011d1ff7)はすべてリスト内包表記で使用できます。

```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](https://gist.github.com/ZhouYang1993/aa860a90a6223a3b4dbe9fbf5289fabc#file-example2-py)

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

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

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

```python
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](https://gist.github.com/ZhouYang1993/78f379eb860e73658757fbb4a35d07f3#file-example3-py)

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

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

```python
Genius = ["Jerry", "Jack", "tom", "yang"]
L1 = [name.capitalize() for name in Genius]
print(L1)
# ['Jerry', 'Jack', 'Tom', 'Yang']
```
[example4.py](https://gist.github.com/ZhouYang1993/adba8b8fe867254bd9820e3ab2562914#file-example4-py)

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

```python
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](https://gist.github.com/ZhouYang1993/5a51bf12e46fdb6b52ad788c227113f1#file-example5-py)

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

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

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

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

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

```python
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](https://gist.github.com/ZhouYang1993/f64b1f6b6a6c4ceef7c658f00b7160bf#file-example6-py)

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

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

```python
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](https://gist.github.com/ZhouYang1993/d91485e3e19526145d1b715aed2e7769#file-example7-py)

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

```python
Genius = ["Jerry", "Jack", "tom", "yang"]
L1 = []
for name in Genius:
    for char in name:
        L1.append(char)
print(L1)
```
[example7_forloop.py](https://gist.github.com/ZhouYang1993/4ed7fd30674a4f4c76c33211df77c1f5#file-example7_forloop-py)

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

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

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

```python
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](https://gist.github.com/ZhouYang1993/0821640366fd1be08c68a8fbc5be8204#file-example8-py)

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

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

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

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

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

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

次は、リスト（`L1`と`L2`）を異なる方法で実装しても、同じ結果になる例です。

```python
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](https://gist.github.com/ZhouYang1993/b6fb5f563bc9cd291f3f0958ba25950f#file-example9-py)

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

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

ジェネレータが遅延評価を適用するため、[ジェネレータ式](https://medium.com/techtofreedom/generators-in-python-f0e59784b3b7)は完全なリストの生成を回避することで、メモリコストを削減できます。

```python
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](https://gist.github.com/ZhouYang1993/b60d4ef8fc150b111a2e4cc48b282011#file-example9-py)

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

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

#まとめ

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

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

翻訳協力

Original Author: [Yang Zhou](https://yangzhou1993.medium.com/)
Original Article: [8 Levels of Using List Comprehension in Python](https://medium.com/techtofreedom/8-levels-of-using-list-comprehension-in-python-efc3c339a1f0)
Thank you for letting us share your knowledge!

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

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