pythonの内包表記を少し詳しく

# pythonの内包表記について

[nbviewer]
(http://nbviewer.jupyter.org/format/slides/github/y-sama/comprehension/blob/master/comprehension.ipynb#/)にも投稿済。

pythonといえば内包表記です。 <= 偏見?
でも、慣れないと読みにくいので、少し詳しく読み方をまとめてみました。


# 通常のリスト生成
```py3
extension_1 = []
for i in range(10):
    extension_1.append(i)
extension_1
#>>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

# リスト内包表記

基本構文
[**counter** for **counter** in **iterator**]
僕は先に [i for i in] だけ書いてから修飾することが多いです。  
extension_1と同等のリストを内包表記で生成する場合は 

```py3
comprehension_1= [i for i in range(10)]
comprehension_1
#>>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

# 速度
リスト内包表記を使うかどうかの実行速度を比べてみます。
jupyterのセルマジック%%timeitを用いて測定しました。

```py3
%%timeit
extension_1 = []
for i in range(10000):
    extension_1.append(i)
#>>> 100 loops, best of 3: 3.37 ms per loop
```

```py3
%%timeit
comprehension_1= [i for i in range(10000)]
#>>> 1000 loops, best of 3: 1.05 ms per loop
```

**リスト内包表記はコードがすっきりするだけでなく速度面でも有利です**
[参考: Pythonの内包表記はなぜ速い？](http://dsas.blog.klab.org/archives/51742727.html)
遅い理由は大きく２つあります。

- ループする度にリストオブジェクトのappendを参照する
- appendをpythonの関数として実行する

前者の影響はループの外に参照を追い出すことでも解消できます。

```py3
%%timeit
extension_1_ = []
append=extension_1_.append
for i in range(10000):
    append(i)
#>>> 1000 loops, best of 3: 1.57 ms per loop
```

一般にリスト内包表記にすると実行速度が2倍になると言われていますが、その8割くらいはappendメソッドの呼び出し部分のオーバーヘッドによるものです。

# ifを含む場合(後置if)
pythonには後置if文がありませんが、リスト内包表記に限っては（結果的にですが）書けます。

```py3
extension_2 =[]
for i in range(10):
    if i%2==0:
        extension_2.append(i)
extension_2
#>>> [0, 2, 4, 6, 8]
```

extension_2をリスト内包表記で書きなおすと下記のような感じです。

```py3
comprehension_2 = [i for i in range(10) if i%2==0]
comprehension_2
#>>> [0, 2, 4, 6, 8]
```

結果的に後置ifの構文になっていますが、これは内包表記ではfor節の後にif節やfor節がつなげられるためです。(コロンとインデントを省略できると思えばよい。)

インデントつけると下記のようなイメージです。

```py3
[
i
for i in range(10)
  if i%2==0
]
```
[闇編](http://qiita.com/y__sama/items/a2c458de97c4aa5a98e7#%E9%97%87%E3%81%B8%E3%81%AE%E9%81%93)でもう少し詳しく説明します。

```
%%timeit
extension_2 =[]
for i in range(10000):
    if i%2==0:
        extension_2.append(i)
#>>> 100 loops, best of 3: 4.08 ms per loop
```
```
%%timeit
comprehension_2 = [i for i in range(10000) if i%2==0]
#>>> 100 loops, best of 3: 3.15 ms per loop
```
```
%%timeit
extension_2_ =[]
append=extension_2_.append
for i in range(10000):
    if i%2==0:
        append(i)
#>>> 100 loops, best of 3: 3.81 ms per loop
```

**実はifが計算律速なので、無理にリスト内包表記にしても20-30%くらいしか速度が改善しません。**

# if ~ elseを含む場合 (条件演算子)
紛らわしいですが、**else節を含む場合は条件演算子（他で言う三項演算子）を使うのでifの位置が変わります**
（条件演算子はpython 2.5以降のみ対応です）

```py3
extension_3 =[]
for i in range(10):
    if i%2==0:
        extension_3.append(i)
    else:
        extension_3.append(str(i))
extension_3
#>>> [0, '1', 2, '3', 4, '5', 6, '7', 8, '9']
```

```
comprehension_3 = [ i if i%2==0 else str(i) for i in range(10)]
comprehension_3
# >>> [0, '1', 2, '3', 4, '5', 6, '7', 8, '9']
```

実際にはこちらと等価だと思えば理解しやすいかもしれません

```py3
extension_3_cond =[]
for i in range(10):
    extension_3_cond.append(i) if i%2==0 else extension_3_cond.append(str(i))
extension_3_cond
#>>> [0, '1', 2, '3', 4, '5', 6, '7', 8, '9']
```

後置ifの書き方に引っ張られて、if~elseを後につけるとエラーが出ます。

```py3
# 動きません
[ i for i in range(10) if i%2==0 else str(i)]
#>>> SyntaxError: invalid syntax
```

if~elseが律速になるので、あまり内包表記による高速化はありません。

# 辞書内包表記とセット内包表記

python2.7以降ではリスト以外の内包表記として、辞書内包やセット内包も使えます。

```py3
comprehension_dict = {str(i):i for i in range(10)}
print(comprehension_dict)
#>>> {'7': 7, '8': 8, '2': 2, '9': 9, '0': 0, '1': 1, '6': 6, '5': 5, '4': 4, '3': 3}
```

zipとかと相性いいです。

```py3
label = ["kinoko", "takenoko", "suginoko"]
feature = ["yama", "sato", "mura"]
{i:j for i,j in zip(label,feature)}
#>>> {'kinoko': 'yama', 'suginoko': 'mura', 'takenoko': 'sato'}
```

> このケースのように単純にkeyとvalueを渡すだけであれば、内包表記を使う必要はありませんが。
> `dict(zip(label,feature))`

python2.6まではdictにtupleを渡してあげます。

```py3
comprehension_dict2 = dict((str(i),i) for i in range(10))
print(comprehension_dict2)
```

後置ifも使えます。

```py3
comprehension_dict2 = {str(i):i for i in range(10) if i%2==0}
print(comprehension_dict2)
#>>> {'8': 8, '6': 6, '2': 2, '4': 4, '0': 0}
```

条件演算子も使えます。
条件演算子なので、「:」の前後のkeyとvalueそれぞれに記載する必要があります。

```py3
comprehension_dict3 = {str(i) if i%2==0 else i : i if i%2==0 else str(i) for i in range(10)}
print(comprehension_dict3)
#>>> {'2': 2, 1: '1', 3: '3', 5: '5', '0': 0, 7: '7', 9: '9', '6': 6, '4': 4, '8': 8}
```
これは動かないです。(以前やりましたorz)

```py3
#動きません
comprehension_dict4 = {str(i):i if i%2==0 else i:str(i) for i in range(10)}
#>>> SyntaxError: invalid syntax
```

## セット内包表記
コロンなしで {} で囲めばセット内包表記になります。

```py3
comprehension_set={i%5 for i in range(10)}
comprehension_set
#>>> {0, 1, 2, 3, 4}
```

要素ゼロの{}は辞書を意味するので注意。

```py3
zero_set={}
type(zero_set)
# >>> dict
```

# ジェネレータ式とタプル内包表記
構文から勘違いしやすいですが、()で囲んでもタプル内包表記にならずにジェネレータ式になります。

```py3
comprehension_gen=(i%5 for i in range(10))
comprehension_gen
#>>> <generator object <genexpr> at 0x7f3000219678>

for i in comprehension_gen:print(i)
#>>> 0
#>>> 1
#>>> 2
#>>> 3
#>>> 4
#>>> 0
#>>> 1
#>>> 2
#>>> 3
#>>> 4
```

むしろタプル内包表記より使います。
リストと違ってメモリ中に全要素を格納しないで、次の要素を順番に生成します。
内包表記を使わない書き方は下記ですが、一旦　ジェネレータを生成する関数を作らないと行けないので面倒です。

```py3
def gen_func():
    for i in range(10):
        yield i%5
extension_gen = gen_func()
extension_gen
#>>> <generator object gen_func at 0x7f3000166830>
```

タプル内包表記が必要なことはあまりないですが、もしどうしても必要ならリスト内包表記をtuple関数に渡せば作れます。

```py3
tuple([i for i in range(10)])
#>>> (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
```

[追記]
コメントを頂いた内容を追記します。
渡すのはジェネレータでもよく、ジェネレータはかっこを省略できるので可読性に優れると思います。（タプル内包表記っぽい書き方になりますし。）

```py3
tuple(i for i in range(10))
#>>> (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

# 以下の構文のカッコの省略形
tuple((i for i in range(10)))
```

# 関数型プログラミングとの比較

map,filterと内包表記はアウトプットが近く、よく比較されます。
また、python 2系ではmap,filterはリストを返すため、リスト内包表記と対応します。
python 3系ではmap,filterオブジェクトというイテレータを返すため、ジェネレータと対応します。

mapでの書き換え

``` python
map(lambda i:i**2,range(1,11))
# python2.xの場合
#>>> [1, 4, 9, 16, 25, 36, 49, 64, 100]
# python3.xの場合は返ってくるのはマップオブジェクトという名前のイテレータ
#>>> <map at 0x4570f98>

list(map(lambda i:i**2,range(1,11)))
#>>> [1, 4, 9, 16, 25, 36, 49, 64, 100]

#リスト内包表記だと
[i**2 for i in range(1,11)]
#>>> [1, 4, 9, 16, 25, 36, 49, 64, 100]
```

filterでの書き換え

```python
filter(lambda i:i%2==1, range(1,11))
# python2.xの場合
#>>> [1, 3, 5, 7, 9]
# python3.xの場合は返ってくるのはフィルターオブジェクトという名前のイテレータ
#>>> <filter at 0x4578a20>

list(filter(lambda i:i%2==1, range(1,11)))
#>>> [1, 3, 5, 7, 9]
#リスト内包表記だと
[i for i in range(1,11) if i%2==1]
#>>> [1, 3, 5, 7, 9]
```

ネストすると

```python
map(lambda j:j**2, filter(lambda i:i%2==1, range(1,11)))
# python2.xの場合
#>>> [1, 9, 25, 49, 81]
# python3.xの場合は返ってくるのはフィルターオブジェクト
#>>> <filter at 0x4578a20>

list(map(lambda j:j**2, filter(lambda i:i%2==1, range(1,11))))
#>>> [1, 9, 25, 49, 81]

#リスト内包表記だと
[i**2 for i in range(1,11) if i%2==1]
#>>> [1, 9, 25, 49, 81]
```

僕はリスト内包表記の方が読みやすいんですが、慣れの問題ですかね?
また、一般的にはリスト内包表記の方が高速です。

# 闇への道

**この節を読み終えた後は[リーダブルコード](http://www.amazon.co.jp/dp/4873115655)を読んで心を浄化することを推奨します。**
基本的な内包表記以外の多段や条件分岐などは使わない方がpythonicな気がします。

## 条件演算子を複数つなげる
条件演算子を複数つなげる
fizzbuzzとかを例にするとこんな感じ。

```py3
fizzbuzz=[]
for i in range(1,16):
    if i%15==0:
        fizzbuzz.append("fizzbuzz")
    elif i%3==0:
        fizzbuzz.append("fizz")
    elif i%5==0:
        fizzbuzz.append("buzz")
    else:
        fizzbuzz.append(i)
#>>> [1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz']
```

```py3
["fizzbuzz" if i%15==0 else "fizz" if i%3==0 else "buzz"
 if i%5==0 else i for i in range(1,16)]
#>>> [1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz']
```

## ネスト(多重配列)

配列をネストしたいケースは結構あると思います。
リスト内包表記にリスト内包表記をネストすればできます。

```py3
outer_list=[]
for i in range(3):
    innter_list=[]
    for j in range(10):
        innter_list.append(j)
    outer_list.append(innter_list)
outer_list
#>>> [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
#>>>  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
#>>>  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]

# リスト内包表記で書くと
[[j for j in range(10)] for i in range(3)]
#>>> [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
#>>>  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
#>>>  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
```

三重あたりになるとpythonなのに読みにくくなります。

## 二重ループ
flattenを例に取ります。
一度理解すればそれほど読みにくくないのですが、
初見だと可読性が著しく悪くなります。

```py3
init=[[1,2,3],[4,5],[6,7]]
flatten=[]
for outer in init:
    for inner in outer:
        flatten.append(inner)
flatten
#>>> [1, 2, 3, 4, 5, 6, 7]

# これと等価
[inner for outer in init for inner in outer]
#>>> [1, 2, 3, 4, 5, 6, 7]
```

基本的には左側のfor節から順番に読んでいって、最後にリストに入れるものが頭に来ます。
インデントするならこんな感じです。

```py3
[
inner
for outer in init
    for inner in outer
]
#>>> [1, 2, 3, 4, 5, 6, 7]
```

[ifを含む場合(後置if)節で示した](http://qiita.com/y__sama/items/a2c458de97c4aa5a98e7#if%E3%82%92%E5%90%AB%E3%82%80%E5%A0%B4%E5%90%88%E5%BE%8C%E7%BD%AEif)のと同様に、for節毎にコロンとインデントを省略したような挙動を示します。

### パタトクカシーー

zipと二重ループを組み合わせればワンライナーでパタトクカシーを生成できます。

```py3
patato=[]
for i in zip("パトカー","タクシー"):
    for j in i:
        patato.append("".join(j))
"".join(patato)
#>>> 'パタトクカシーー'

# 内包表記で書くと
"".join(["".join(j) for i in zip("パトカー","タクシー") for j in i])
#>>> 'パタトクカシーー'

# インデントすると
"".join(
  [
  "".join(j)
  for i in zip("パトカー","タクシー")
    for j in i
  ]
)
#>>> 'パタトクカシーー'
```

printとか入れてもできます。

```py3
[print(k) for i in zip("パトカー","タクシー") for j in i for k in j]
#> パ
#> タ
#> ト
#> ク
#> カ
#> シ
#> ー
#> ー
#>>> [None, None, None, None, None, None, None, None]
# print関数の返り値はないので、Noneの詰め合わせになります。
```

## if 節との多重ループの合わせ技
なかなかです。
これに更に多重配列とかlambdaとか入れるといい感じの可読性になります。

```py3
import re
DIO=["U","無駄","RR","貧弱ゥ","Y"]

rslt=[]
for i in DIO:
    if re.match("[URY]+",i):
        for j in i:
            rslt.append(j*2)
"".join(rslt)
#>>> 'UURRRRYY'

# 内包表記
"".join([j*3 for i in DIO if re.match("[URY]+",i) for j in i])
#>>> 'UUURRRRRRYYY'

# インデントするとこんな感じ
"".join(
  [
    j*4
    for i in DIO
      if re.match("[URY]+",i)
        for j in i
  ]
)
#>>> 'UUUURRRRRRRRYYYY'
```