pythonっぽいコードを書く

Youtubeで[Raymond Hettingerさん](https://twitter.com/raymondh)（python core developper）がpythonでよくあるミスと正しい書き方を解説する[動画](https://www.youtube.com/watch?v=OSGv2VnC0go)を見つけたので、まとめます。
動画では、主にpython2を例に挙げてますが（2013年の動画なので）、ここではできるだけpython3に変換しました。今ではすでに古い書き方になっている場合があるので適宜調べながら使ってください。

## Loop

- できるだけiteratorを使う

#### シンプルなloop

```python:だめな例
for i in [0, 1, 2, 3, 4, 5]:
  print(i**2)
```
リスト全体をメモリにおいてしまう。
↓

```python:良い例
for i in range(6):
  print(i**2)
```
`range`がiteratorとして一つずつ生成するので、メモリを無駄に使わない。

> python2では`range`はリスト、`xrange`がitertor
> python3では`range`がiterator（python2の`xrange`の名前が変わった）

#### リストのloop

```python:だめな例
colors = ['red', 'green', 'blue', 'yellow']

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

```python:良い例
colors = ['red', 'green', 'blue', 'yellow']

for color in colors:
  print(color)
```

> 下の書き方のほうが速い

#### 逆向きのloop

```python:だめな例
colors = ['red', 'green', 'blue', 'yellow']

for i in range(len(colors)-1, -1, -1):
  print(colors[i])
```
↓

```python:良い例
colors = ['red', 'green', 'blue', 'yellow']

for color in reversed(colors):
  print(color)
```

#### インデックスも得たい

```python:だめな例
colors = ['red', 'green', 'blue', 'yellow']

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

```python:良い例
colors = ['red', 'green', 'blue', 'yellow']

for i, color in enumerate(colors):
  print(i, '--->', color)
```

#### ２つのリストを同時にloop

```python:だめな例
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']

n = min(len(names), len(colors))
for i in range(n):
  print(names[i], '--->'. colors[i]
```
↓

```python:良い例
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']

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

> 長さの違うiteratorがzipに入力されると、短い方に揃う。
> 長い方に揃えるためには`itertools.zip_longest`

> python2では`zip`はリストを生成（`izip`がiterator）、
> python3では`zip`がiteratorを生成

#### カスタムソート

```python:だめな例
colors = ['red', 'green', 'blue', 'yellow']

def compare_length(c1, c2):
  if len(c1) < len(c2):
    return -1
  elif len(c1) > len(c2):
    return 1
  else:
    return 0

print(sorted(colors, cmp=compare_length)
```
↓

```python:良い例
colors = ['red', 'green', 'blue', 'yellow']

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

> **keyによるソートで十分か？**
> 十分でない例もあるがほとんどの場合は大丈夫。（SQLはソートを大量にやるが、keyによるソートでやっている）

#### sentinel valueでループを止める
```python:だめな例
blocks = []
while True:
  block = f.read(32)
  if block == '':
    break
  blocks.append(block)
```
↓

```python:良い例
blocks = []

for block in iter(functool.partial(f.read, 32), ''):
  blocks.append(block)
```

> partialが気持ち悪いが、iteratorとして扱えることのメリットが大きい
> sentinel value（終了を示す値）はさけたほうが良い

#### 条件によってループから抜ける

```python:だめな例
def find(seq, target):
  found = False
  for i, value in enumerate(seq):
    if value == target:
      found = True
      break
  if not found:
    return -1
  return i
```
フラッグ（`found`）を使わなきゃいけない場合の例
↓

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

> for内でbreakがなかったらelse文が実行される。elseではなく`nobreak`という名前にすべきだったと後悔しているそう。

## Dictionary

#### Dictionaryのキーをループ

```python
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k in d:
  print(k)
```
↑辞書に変更を加えるとおかしなことが起こる

```python
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k in d.keys():
  if k.startswith('r'):
    del d[k]
```
↑`d.keys()`が事前にリストのコピーを作るので、辞書の変更が可能

#### キーと値をループ

```python:だめな例
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k in d:
  print(k, '--->', d[k])
```
↓

```python:良い例
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k, v in d.items():
  print(k, '--->', v)
```

#### リストから辞書を作成

```python
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue']

d = dict(zip(names, colors))
```

#### リスト内の出現回数を数える

```python:非効率な方法
colors = ['red', 'green', 'red', 'blue', 'green', 'red']

d = {}
for color in colors:
  if color in d:
    d[color] = 0
  d[color] += 1
```
↓

```python:正しいやり方
d = {}
for color in colors:
  d[color] = d.get(color, 0) + 1
```
↓

```python:最近の方法
d = defaultdict(int)
for color in colors:
  d[color] += 1
```

#### リストをグルーピングする

```python:だめな例
names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']

d = {}
for name in names:
  key = len(name)
  if key not in d:
    d[key] = []
  d[key].append(name)
```
↓

```python:正しいやり方
d = {}
for name in names:
  key = len(name)
  d.setdefault(key, []).append(name)
```
↓

```python:最近の方法
d = defaultdict(list)
for name in names:
  key = len(name)
  d[key].append(name)
```

> `get`は辞書に代入はしない。`setdefault`は代入する

#### 複数の辞書をくっつける

```python:だめな例
defaults = {'color': 'red', 'user': 'guest'}
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args([])
command_line_args = {k: v for k, v in vars(namespace).items() if v}

d = defaults.copy()
d.update(os.environ)
d.update(command_line_args)
```
辞書が大量にコピーされる
↓

```python:良い例
d = collections.ChainMap(command_line_args, os.environ, defaults)
```
コピーせずにもとの辞書をそのまま持つ

## コードの可読性向上

#### 関数のkeyword引数

```python:だめな例
twitter_search('@obama', False, 20, True)
```
引数の意味がわからない
↓

```python:良い例
twitter_search('@obama', retweets=False, numtweets=20, popular=True)
```

#### NamedTuple
```python:だめな例
> doctest.testmod()
(0, 4)
```
0,4の意味がわからない
↓

```python:良い例
> doctest.testmod()
TestResults(failed=0, attempted=4)
```

`TestResults`は

```python
TestResults = namedtuple('TestResults', ['failed', 'attempted'])
```

で作れる

#### tupleのunpacking
```python:だめな例
p = 'Raymond', 'Hettinger', 0x30, 'python@example.com'

fname = p[0]
lname = p[1]
age = p[2]
email = p[3]
```
↓

```python:良い例
fname, lname, age, email = p
```

#### 複数の状態を同時に更新する

```python:だめな例
def fibonacci(n):
  x = 0
  y = 1
  for i in range(n):
    print(x)
    t = y
    y = x + y
    x = t
```
実行途中に状態が崩れている瞬間がある。行の順序を間違えやすい
↓

```python:良い例
def fibonacci(n):
  x, y = 0, 1
  for i in range(n):
    print(x)
    x, y = y, x+y
```
こっちのほうが人間の思考に近い。

## 効率化（高速化、省メモリ）

#### 文字列の結合

```python:だめな例
names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']

s = names[0]
for name in names[1:]:
  s += ', ' + name
```
↓

```python:良い例
', '.join(names)
```

#### リストの更新

```python:だめな例
names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']

del names[0]
names.pop(0)
names.insert(0, 'mark')
```
遅い
↓

```python:良い例
names = deque(['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie'])

del names[0]
names.popleft()
names.appendleft('mark')
```
速い

## decoratorとcontext manager

- business logicとadministrative logicを分ける
- コードがきれいになる
- 正しく名前をつけないとグッチャになる
- With great power comes with great responsibility

#### キャッシュ

```python:だめな例
def web_lookup(url, saved={}):
  if url in saved:
    return saved[url]
  page = urlib.urlopen(url).read()
  saved[url] = page
  return page
```
↓

```python:良い例
@lru_cache()
def web_lookup(url):
  return urllib.urlopen(url).read()
```
business logicとadministrative logicが分離している

#### 一時的なコンテクスト

```python:だめな例
oldcontext = getcontext().copy()
getcontext().prec = 50
print(Decimal(355) / Decimal(113))
setcontext(oldcontext)
```
↓

```python:良い例
with localcontext(Context(prec=50)):
  print(Decimal(355) / Decimal(113))
```

#### ファイルの開閉

```python:だめな例
f = open('data.txt')
try:
  data = f.read()
finally:
  f.close()
```
↓

```python:良い例
with open('data.txt') as f:
  data = f.read()
```

#### スレッドのロック

```python:だめな例
lock = threading.Lock()
lock.acquire()
try:
  print('Critical section 1')
  print('Critical section 2')
finally:
  lock.release()
```
↓

```python:良い例
lock = threading.Lock()
with lock:
  print('Critical section 1')
  print('Critical section 2')
```

#### エラーの無視

```python:だめな例
try:
  os.remove('somefile.tmp')
except OSError:
  pass
```
↓

```python:良い例
with ignored(OSError):
  os.remove('somefile.tmp')
```

#### 標準出力を一時的に置き換え

```python:だめな例
with open('help.txt', 'w') as f:
  oldstdout = sys.stdout
  sys.stdout = f
  try:
    help(pow)
  finally:
    sys.stdout = oldstdout
```
↓

```python:良い例
with open('help.txt', 'w') as f:
  with redirect_stdout(f):
    help(pow)
```

## リストの内包記法

```python:だめな例
result = []
for i in range(10):
  s = i**2
  result.append(a)
print(sum(result))
```
↓

```python:マシな例
print(sum([i**2 for i in range(10)])
```
↓

```python:良い例
print(sum(i**2 for i in range(10)))
```