内包表記のあれやこれ。

「Python使ってて内包表記もろくに書けないんじゃだめだろ」

と偉い人に言われたのでいろんなサイト見て、お勉強。  
（前提知識は「え、リスト内包だけじゃないんだ！？」レベル）

# 内包的記法と外延的記法

* 外延的記法(extensional definition)…具体的に書く  
    ex) {1,3,5,7,9}
* 内包的記法(intensional definition)…性質を書く  
    ex) {x|x∈ℕ,x<10,x%2=1}

[Wikipedia - 内包と外延](https://ja.wikipedia.org/wiki/%E5%86%85%E5%8C%85%E3%81%A8%E5%A4%96%E5%BB%B6)

# Pythonの内包表記4パターン

## 1.リスト内包

### 通常のリスト生成


```python
extension_li = []
for x in range(10):
    extension_li.append(x**2)
extension_li
```


    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]



### リスト内包表記
[]で定義する。リストを返す。


```python
comprehension_li = [x**2 for x in range(10)]
comprehension_li
```

    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]


## 2.セット内包
{}で定義する。

```python
comprehension_set = {i/2 for i in range(1,11)}
comprehension_set
```

    {0.5, 1.0, 2.0, 2.5, 1.5, 3.0, 3.5, 4.0, 4.5, 5.0}


## 3.辞書内包

```python
abb = ("NY","TX","MA","WA","CT")
state = ("New York", "Texas", "Massachusetts", "Washington", "Connecticut")

comprehension_dict = {i:j for i,j in zip(abb,state)}
comprehension_dict
```

    {'WA': 'Washington',
     'TX': 'Texas',
     'NY': 'New York',
     'MA': 'Massachusetts',
     'CT': 'Connecticut'}


## 4.ジェネレータ式
()で定義する。決してタプル内包表記にはならず、返ってくるのは要素を生成するジェネレータ。リストのようにメモリ中に全要素を格納しない分、メモリの節約になる。

```python
comprehension_gen = (i%3 for i in range(1,10))
comprehension_gen
```

    <generator object <genexpr> at 0x014CD900>

```python
for i in comprehension_gen:print(i)
```

    1
    2
    0
    1
    2
    0
    1
    2
    0
    

# ifの場所

## 単純にif文を含む場合


```python
extension = [i for i in range(1,10) if i%2==0]
extension
```

    [2, 4, 6, 8]

## if~else文となる場合

```python
comprehension = [i if i%2==0 else 0 for i in range(1,10)]
comprehension
```

    [0, 2, 0, 4, 0, 6, 0, 8, 0]

elseが入ると条件表記が先に来る。
理由は明確で、「単純にif文の場合」は通常の内包表記の文法であるのに対して「if~else文の場合」は三項演算子として処理されているため。

ちなみに、pythonの三項演算子は  

**「　(条件がTrueのときの値) if (条件) else (条件がFalseのときの値)　」**  

という記述になる。（Java,C言語などとは異なるので注意が必要）

散々使い使い古されたネタで恐縮だが、FizzBuzzもif~else文を使えば容易にワンライナーで書ける。

```python
print(["FizzBuzz" if n % 15 == 0 else "Fizz" if n % 3 == 0 else "Buzz" if n % 5 == 0 else n for n in range(1,101)])
```

あるいはこんな書き方もできる。


```python
print([(i%3==0 and 1 or 0)*"Fizz"+(i%5==0 and 1 or 0)*"Buzz" or i for i in range(1, 101)])
```

※ @shiracamus さんからのコメントより、次のように真は1、偽は0として演算できるので、下のように書いたほうが見やすいです。（結果はもちろん同じですが）

```python
print([(i%3==0)*"Fizz"+(i%5==0)*"Buzz" or i for i in range(1, 101)])
```

    [1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz', 11, 'Fizz', 13, 14, 'FizzBuzz', 16, 17, 'Fizz', 19, 'Buzz', 'Fizz', 22, 23, 'Fizz', 'Buzz', 26, 'Fizz', 28, 29, 'FizzBuzz', 31, 32, 'Fizz', 34, 'Buzz', 'Fizz', 37, 38, 'Fizz', 'Buzz', 41, 'Fizz', 43, 44, 'FizzBuzz', 46, 47, 'Fizz', 49, 'Buzz', 'Fizz', 52, 53, 'Fizz', 'Buzz', 56, 'Fizz', 58, 59, 'FizzBuzz', 61, 62, 'Fizz', 64, 'Buzz', 'Fizz', 67, 68, 'Fizz', 'Buzz', 71, 'Fizz', 73, 74, 'FizzBuzz', 76, 77, 'Fizz', 79, 'Buzz', 'Fizz', 82, 83, 'Fizz', 'Buzz', 86, 'Fizz', 88, 89, 'FizzBuzz', 91, 92, 'Fizz', 94, 'Buzz', 'Fizz', 97, 98, 'Fizz', 'Buzz']
    

# mapやfilterで代用できない？
答え　できる。

ここでは例として[1,2,3,4,5,6,7,8,9,10]の各値を二乗したリストを作る操作を比較する。


```python
%%timeit
list(map(lambda i : i**2, range(1,11)))
```

    10000 loops, best of 3: 23.8 µs per loop
    
```python
%%timeit
[i**2 for i in range(1,11)]
```

    100000 loops, best of 3: 15.8 µs per loop
    
   
filterに関しては[1,2,3,4,5,6,7,8,9,10]から偶数を抜く操作を比較する。
  

```python
%%timeit
list(filter(lambda i : i%2==0, range(1,11)))
```

    10000 loops, best of 3: 19.3 µs per loop
    

```python
%%timeit
[i for i in range(1,11) if i%2==0]
```

    100000 loops, best of 3: 10.2 µs per loop
    

見てもらって分かるように速度が違う。可読性も（何をしているのかすぐわかるという意味では）内包表記のほうに軍配が上がる（個人差）。

# 速度の話
処理速度の話が出たのでついでに。
最初に出たリスト内包表記と通常のリスト生成では速度を比較してみるとどうなるか。

```python
def for_loop(n):
    extension = []
    for x in range(n):
        extension.append(x**2)
    return extension
def comprehension(n):
    return [x**2 for x in range(n)]
```


```python
%timeit  for_loop(1000)
```

    1000 loops, best of 3: 1.58 ms per loop
    
```python
%timeit  comprehension(1000)
```

    1000 loops, best of 3: 1.16 ms per loop
    

僕の低スぺPCだとあまり差は実感しづらいが、それでも違いは明確。  
理由は簡単に言ってしまえば前者はfor文を回すたびにappend属性の取り出しに時間がかかっているため。  
物は試しで、append属性の参照だけfor文の外に出してみて時間を測る。


```python
def for_loop2(n):
    extension = []
    app = extension.append
    for x in range(n):
        app(x**2)
    return extension
```


```python
%timeit for_loop2(1000)
```

    >>>1000 loops, best of 3: 1.27 ms per loop
    

予想通り処理時間は、for_loop>for_loop2>comprehensionの順になった。  
for_loop2とcomprehensionの差はつまりはappend自体の速さを意味しているのだろう。

# さいごに
なんとなくではあるが内包表記の扱い方がわかった。  

これで今日からPythonマスター…とは行くわけもなく…。

というかこの内包表記python特有の書き方なのかなと思っていたがほかの多くの関数型言語では普通に使うものらしい。

それら含めて参考とさせていただいたサイトを載せておく。

## ~参考~
[DSAS開発者の部屋-Pythonの内包表記はなぜ速い？](http://dsas.blog.klab.org/archives/51742727.html)  
[Qiita - pythonの内包表記を少し詳しく](http://qiita.com/y__sama/items/a2c458de97c4aa5a98e7)  
[stackoverflow - if else in a list comprehension](http://stackoverflow.com/questions/4406389/if-else-in-a-list-comprehension)  
[知に至る病 - Python の内包表記で if ～ else を使った場合分けをする](http://amano41.hatenablog.jp/entry/python-if-else-in-list-comprehension)  
[Life with Python - Pythonの内包表記の使い方まとめ](http://www.lifewithpython.com/2014/09/python-list-comprehension-and-generator-expression-and-dict-comprehension.html)