Effective Python 学習備忘録 7日目 【7/100】

## はじめに
Twitterで一時期流行していた 100 Days Of Code なるものを先日知りました。本記事は、初学者である私が100日の学習を通してどの程度成長できるか記録を残すこと、アウトプットすることを目的とします。誤っている点、読みにくい点多々あると思います。ご指摘いただけると幸いです！

## 今回学習する教材

- [Effective Python](https://www.amazon.co.jp/Effective-Python-%E2%80%95Python%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%82%92%E6%94%B9%E8%89%AF%E3%81%99%E3%82%8B59%E9%A0%85%E7%9B%AE-Brett-Slatkin/dp/4873117569/ref=sr_1_3?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=Effective+python&qid=1609408832&sr=8-3)
    - 8章構成
    - 本章216ページ

## 今日の進捗

- 進行状況：61-64ページ
- 第3章：クラスと継承
- 本日学んだことの中で、よく忘れるところ、知らなかったところを書いていきます。


### 単純なインターフェースにはクラスの代わりに関数を使う

Pythonには、関数を引き渡すことによって振る舞いをカスタマイズできる組込みAPIが多数ある。このような仕組みを**フック**と呼び、APIは、実行中にそのコードをコールバックする。
例えば、defaultdictクラスをカスタマイズしたい場合、このデータ構造では、キーが見つからなかったら、そのたびに呼ばれる関数を与えることができる。キーが見つからないとログを取り、デフォルト値として0を返すフックを次のように定義する。

``` code
def log_missing():
    print('Key added')
    return 0
```

初期状態の辞書と追加データ集合を使って、log_missingが実行される。

``` code
from collections import defaultdict

current = {'green': 10, 'blue': 2}
increments = [
    ('red', 3),
    ('blue', 15),
    ('orange', 8)
]
result = defaultdict(log_missing, current)
print('Before: ', dict(result))

for key, amount in increments:
    result[key] += amount     # resultに値が入るたびにlog_missingが呼び出される
print('After: ', dict(result))
```

出力結果

``` result
Before:  {'green': 10, 'blue': 2}
Key added
Key added
After:  {'green': 10, 'blue': 17, 'red': 3, 'orange': 8}
```

defaultdictに渡すデフォルト値のフックを見つからないキーの全個数を数えるようにするヘルパー関数を以下のように定義する。

``` code
from collections import defaultdict

def increment_with_report(current, increments):
    added_count = 0

    def missing():
        nonlocal added_count  # クロージャの状態
        added_count += 1      # 呼び出されるたびに、1増やす
        return 0

    result = defaultdict(missing, current)
    for key, amount in increments:
        result[key] += amount

    return result, added_count

current = {'green': 10, 'blue': 2}
increments = [
    ('red', 3),
    ('blue', 15),
    ('orange', 8)
]

result, count = increment_with_report(current, increments)
print(count)
```

出力結果

``` result
2
```

他の方法として、カプセル化したクラスを定義する方法がある。

``` code
from collections import defaultdict

class CountMissing(object):
    def __init__(self):
        self.added = 0

    def missing(self):
        self.added += 1
        return 0

current = {'green': 10, 'blue': 2}
increments = [
    ('red', 3),
    ('blue', 15),
    ('orange', 8)
]

counter = CountMissing()
result = defaultdict(counter.missing, current)

for key, amount in increments:
    result[key] += amount
print(counter.added)
```

出力結果

``` result
2
```

CountMissngクラスはdefaultdictが呼び出されるまで、何を目的としたクラスなのか理解が困難である。このような難解さを取り除くために、Pythonは、特殊メソッド __call__ を定義できる。__call__は、オブジェクトが関数のように呼び出すことを許可する。

``` code
from collections import defaultdict

class BetterCountMissing(object):
    def __init__(self):
        self.added = 0

    def __call__(self):
        self.added += 1
        return 0


current = {'green': 10, 'blue': 2}
increments = [
    ('red', 3),
    ('blue', 15),
    ('orange', 8)
]

counter = BetterCountMissing()
result = defaultdict(counter, current)    # __call__により、メソッドではなくクラスを直接渡せるようになる
for key, amount in increments:
    result[key] += amount
print(counter.added)
```

出力結果

``` result
2
```