Pythonらしいコードを書く（ループ編）

# はじめに
Pythonを学習するにあたって、せっかくなので、Pythonらしい書き方というものを調べてみました。
Pythonらしさは、Pythonの効率の良いコードと同意だからです。

参考にしたスライド
https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1

## ループ

インデックスを使った昔ながらのループから、Pythonらしい文法でループを表現します。

### 整数のループ

Javaなどの基本的なfor文 `for (i=0; i<6; i++)` はPythonにはありません。
代わりに数値の配列を使います。

```
for i in [0, 1, 2, 3, 4, 5]:
    print(i ** 2)
```

整数の範囲なら `range` を使うのがPython風なので、置き換えます。
Python3の `range` はメモリ確保しない動作をしますが、それ以前は値が大きい場合にメモリを消費してしまうので、 `xrange`がおすすめです。

```
for i in range(6):
    print(i ** 2)
```

### コレクションのループ

最近ではコレクションを使った簡単なループがあるのであまりやりませんが、プログラムを学習しはじめたばかりだと、ついインデックスを使いたくなってしまいます。

```
colors = ['red', 'blue', 'yellow', 'green', 'pink']

for i in range(len(colors)):
    print(colors[i])
```

これは理解しやすいのですが、どうしても複雑になってしまって読みにくいコードになってしまいます。
Pythonの `for` は `forEach` なので、以下のように書くと良いです。
また、ループ中の変数については、リストを複数形に、リストの要素を単数形にすることで、何がループしているのかをわかりやすくしておくことがお作法です。

```
colors = ['red', 'blue', 'yellow', 'green', 'pink']
for color in colors:
    print(color)
```

#### 逆順にリストをループする
逆順にするには、リストを逆順にする処理 `reversed` を加えることで実施します。
なにがおきているかわかりやすいです。

```
colors = ['red', 'blue', 'yellow', 'green', 'pink']
for color in reversed(colors):
    print(color)
```

#### 要素といっしょに要素番号（インデックス）を取得する
これは結構必要なパターンで、要素と同時にインデックスがとれるものです。
順序を表示したいときは少なくありません。

```
colors = ['red', 'blue', 'yellow', 'green', 'pink']
for i, item in enumerate(colors):
    print(i, '-->', item)
```

#### ２つのリストを同時にループする
少ない方のリスト分ループします。
昔ながらなの方法だとインデックスを使う方法がわかりやすいかと思います。

```
colors = ['red', 'blue', 'green', 'yellow', 'pink']
names = ['aka', 'ao', 'midori']

n = min(len(colors), len(names))

for i in range(n):
    print(names[i], '-->', colors[i])
```

Pythonでは`zip`を使って簡単に表現できます。

```
colors = ['red', 'blue', 'green', 'yellow', 'pink']
names = ['aka', 'ao', 'midori']

for name, color in zip(names, colors):
    print(name, ' => ', color)
```

`zip`は扱うリストのサイズが大きいとメモリを大きく確保するという問題もあります。
`python3`では改善されているようですが、それ以前のバージョンの場合は`itertools`モジュールにある`izip`に置き換えると良いようです。

#### ソートする
要素をソートしてループします。逆順も可能です。

```
colors = ['red', 'blue', 'green', 'yellow', 'pink']
# 昇順
for color in sorted(colors):
    print(color)
# 逆順
for color in sorted(colors, reverse=True):
    print(color)
```

##### ソート順をもっと複雑に
ソート順をもっと複雑にしたい場合は関数を割り当てることも可能です。
戻り値の大きさでソート順が決まります。

```
colors = ['red', 'blue', 'green', 'yellow', 'pink']

def compare_length(c1):
    return len(c1)

print(sorted(colors, key=compare_length))
```

長さ順なら関数を定義するような面倒なことをせず、直接`len`を指定することも可能です。
また、`key`にはラムダ式を指定することもできます。

```
print(sorted(colors, key=len))
print(sorted(colors, key=lambda x:len(x)))
```

##### 番兵（Sentinel）
番兵とは、リスト処理方法の一つです。
リストの最後に特定の値（番兵）を追加することで、構造をシンプルにする手法です。
たとえばファイルの内容をすべてリストに詰め込むとき、読み込んだブロックが空であるかをチェックし、空の場合はファイルの最後尾と判断するようなロジックがあります。
これを理解しやすい方法で書くとこんな感じになります。

```
from functools import partial

blocks = []
with open('mydata.db', 'rb') as f:
    while True:
        block = f.read(32)
        if block == '':
            break
        blocks.append(block)
```

番兵は`Python`ではこんな風に書けます。

```
from functools import partial

blocks = []
with open('mydata.db', 'rb') as f:
    for block in iter(partial(f.read, 64), b''):
        blocks.append(block)
```

##### ループの終了条件
検索系の関数の場合に、検索条件に合致しない場合に「Not Found」を返すといったように、リストの最後まで一致するものがないときに例外値を返したいときがあります。
これまでは以下のようなコードを書いていました。

```
def find(seq, target):
    found = False
    for i, value in enumerate(seq):
        if value == tgt:
            found = True
            break
    if not found:
        return -1
    return i
```

`found`フラグは、`target`が見つかった時に`True`になる変数ですが、このようなフラグ変数が入ると、コードは読みづらくなります。
このような場合に`Python`は、`for`文の中で`else`を使うことでフラグを使わない構文が作成できます。
エレガントです。

```
def find(seq, target):
    for i, value in enumerate(seq):
        if value == tgt:
            break
    else:
        return -1
    return i
```