Level別 Pythonリスト内包表記 8選

リストの内包表記はとても便利な書き方ですが、他の言語からやってくる人にとって
少し理解しづらいものだと感じました。

なのでこれさえ押さえとけば、**簡単な**内包表記は書けちゃうよって物をまとめました。
# 環境
Python 3.7.8

## そもそもリストの内包表記とは
リストを生成する簡潔な手段です。【[公式:リストの内包表記](https://docs.python.org/ja/3/tutorial/datastructures.html#list-comprehensions)】

具体的にはこんな感じ。

```python
sample = [1, 2, 3, 4]
new_sample = [ x * 2 for x in sample]
print(new_sample)

# [2, 4, 6, 8]
```
## level 1 : 基本をおさえよう
リスト内包表記の基本はこれだ。

```python:式
[ expression for item in iterable (if condition) ]
```

実にシンプル。

- iterableオブジェクトをfor文でループ
- expressionでitemデータを加工 
- オプションでifによる条件の絞り込み


## level 2 : まずはfor文を置き替えて

```python
full_name = "Yamada Taro"
name_list = []
for c in full_name:
    name_list.append(c)

print(full_name)
print(name_list)

# Yamada Taro
# ['Y', 'a', 'm', 'a', 'd', 'a', ' ', 'T', 'a', 'r', 'o']
```
上のfor文を内包表記で書き換えてみましょう。

```python
full_name = "Yamada Taro"
characters = [char for char in full_name]

print(full_name)
print(characters)

# Yamada Taro
# ['Y', 'a', 'm', 'a', 'd', 'a', ' ', 'T', 'a', 'r', 'o']
```

2次元リストもitelableとして利用可能。

```python
Matrix = [[7, 1, 5],
          [22, 99, 0],
          [33, 2, 57]]
row_min = [min(row) for row in Matrix]

# [1, 0, 2]
```

## level 3 : optionのifでデータを絞ろう
if文を使えば、データを絞る事ができます。
SQLの**WHERE**句に該当するものです。

4文字より少なく、小文字のものだけに絞ります。

```python
People = ["Taro", "Jiro", 'Wan', "Elepahnt", "Tomas", "ken", "sae"]
target = [name for name in People if len(name) < 4 and name.islower()]
print(target)

# ['ken', 'sae']
```

## level 4 : expression でデータを加工しよう
expressionの所は、pythonの構文であれば何を書いても構いません。
nameを加工する例。

```python
Genius = ["taro", "jiro", "yamada", "ito"]
target = [name.capitalize() for name in Genius]
print(target)

# ['Taro', 'Jiro', 'Yamada', 'Ito']
```

もちろん、expressionに `if..else..` をいれる事も可能です。

```python
People = ["Taro", "Hiroyuki", "ken", "youu"]
>>> answer = [name if name.startswith('y') else 'Not Genius' for name in People]
>>> print(answer)

# ['Not Genius', 'Not Genius', 'Not Genius', 'youu']
```
ここでの注意は、`if...else...` は三項演算子であり、Level 1 で紹介した式の最後の`if` とは異なるという事です。
> [ expression for item in iterable (if condition) ]

この式でのifは抽出条件で、SQLのwhere句と同じものです。
`else` は入れられません。

もしexpressionに `if` を入れるなら、`else` を必ずいれないといけません。
それは、三項演算子だからです。

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

b = 2 if a > 0 else -1
print(b)
# 2
```

## level 5 : ネストされたfor文もリストに入れよう
ネストされたループはどうでしょう。

```python
Genius = ["taro", "jiro", "yamada"]
L = []
for name in Genius:
    for char in name:
        L.append(char)

print(L)
# ['t', 'a', 'r', 'o', 'j', 'i', 'r', 'o', 'y', 'a', 'm', 'a', 'd', 'a']
```
内包表記にすると、こんな感じになります。

```python
Genius = ["taro", "jiro", "yamada"]
L = [char for name in Genius for char in name]
print(L)

# ['t', 'a', 'r', 'o', 'j', 'i', 'r', 'o', 'y', 'a', 'm', 'a', 'd', 'a']
```
ループは2つより多くなると分かりづらくなるので、2つまでが良いと思います。

## level 6 : map, filterを置き換えよう
mapやfilterはfunctionを引数にする事ができますが、
内包表記で書いたほうが分かりやすくなります。

mapは次のように置き換えられます。

```python:map
L = list(map(func, iterable))
# 置き換え可能
L = [func(a) for a in iterable]
```
filterは次のように置き換えられます。

```python:filter
L = list(filter(condition_func, iterable))
# 置き換え可能
L = [a for a in iterable if condition]
```

実際に例を見てみましょう。

```python:mapの例
People = ["Kenny", "Tomas", "tom", "young"]
L = map(lambda a: a.lower(), People)
print(list(L))
# ['kenny', 'tomas', 'tom', 'young']

L = [a.lower() for a in People]
print(L)
# ['kenny', 'tomas', 'tom', 'young']
```

```python:filterの例
People = ["Kenny", "Tomas", "tom", "young"]
L1 = filter(lambda a: len(a) < 4, People)
print(list(L1))
# ['tom']

L2 = [a for a in People if len(a) < 4]
print(L2)
# ['tom']
```

## level 7 : サイズが大きくなる場合は、generator式を使おう
大量の要素を含むものはジェネレーター式を検討しよう。
ジェネレーター式は実際必要になった時に生成されるので、メモリのコストを大幅に削減できます。
(でも、任意の要素にはアクセスできない。先頭から順番のアクセスのみ。)

```python
# 100万の要素を生成
>>> 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__())
8697440
>>> print(large_list_g.__sizeof__())
96
>>>
```

## level 8 : 複雑と感じるなら無理して使わない。
下記2つは同じリストを生成しますが、どちらが直感的に分かりやすいでしょうか。

```python:内包表記
list_2d = [
     [0, 0, 0],
     [1, 1, 1],
     [2, 2, 2],
]
flat = [num ** 2 if num % 2 == 0 else num for row in list_2d for num in row]
print(flat)

# [0, 0, 0, 1, 1, 1, 4, 4, 4]
```

```python:forループ
list_2d = [
     [0, 0, 0],
     [1, 1, 1],
     [2, 2, 2],
]
flat = []
for row in list_2d:
    for num in row:
        flat.append(num ** 2 if num % 2 == 0 else num)

print(flat)

# [0, 0, 0, 1, 1, 1, 4, 4, 4]
```
私は`forループ`の方が直感的で分かりやすいと思いますが、人によって違うと思います。
この例では単純な2重ループでしたが、条件文がついたり、expressionでデータを加工したり、forループが増えたりして、複雑だと感じたら内包表記を使わない方が良いのではないでしょうか。

## さいごに
内包表記は簡潔にリストの記述をできますが、他者が逆に理解しづらくなってしまっては本末転倒なので、チーム内の様子を見ながら利用するのが良いのではないでしょうか。

level1 の式を押さえておくのはお勧めです。

以上:tangerine: