Effective Pythonはその名の通りEffectiveだった

# あらまし

[Effective Python](https://www.oreilly.co.jp/books/9784873117560/)をかいつまんで読みました. とても良い本だと思います. 自分はPython初級者だと思っているのですが, その自分がある程度じっくり考えて腑に落ちるレベルの事柄が載っていることから, 初級者と中級者の橋渡しをするという役目を十二分に果たしていると思います. 筆者はGoogleで大規模な開発に従事している方なので, 小さいコードばかり組んでいる自分には直感的に利便性が理解しづらい説明もいくらかあります. でもそれはBrett Slatkinのせいではありません. 自身の至らぬが故.

読んだだけでは忘れてしまいそうなので. 感心したポイントの一部をまとめておきたいと思います. ここで使用しているコードは[Github](https://github.com/bslatkin/effectivepython)[^1]にあります.


# 感心ポイント要約(販促)
一部抜粋して載せているので文脈が捉えづらいところもあると思いますが, 是非買って読んでみてください.

## ジェネレータ式を使いなさい！

ジェネレータ式を使って大量の入力を逐次的に処理することができます:

```py3
import random

with open('my_file.txt', 'w') as f:
    for _ in range(10):
        f.write('a' * random.randint(0, 100))
        f.write('\n')

it = (len(x) for x in open('my_file.txt'))
print(next(it))
print(next(it))
```
これをリストやタプルで一度に行うと大量のメモリを消費してしまいます. 割とよく知られている方法だとは思いますが, [エキスパートPythonプログラミング](http://ascii.asciimw.jp/books/books/detail/978-4-04-868629-7.shtml)でも同様にジェネレータ式が激推しされているのが印象的でした.

## `for...else, while...else`は使うべからず
使いどころがいまいちピンと来ない`for...else`ですが, 結局使わないのがベストプラクティスのようです. 理由は, 一般的な`else`と文脈が異なるから. 前のブロックが失敗したら`else`に飛ぶのがふつうですが,`for...else`は`for`文が正常に(`break`されずに)終了したら `else` ブロックを実行します. これが可読性を損なうとのことです. 

最初読んだ時は「なんだその程度のこと...」と思いましたが, **「Effective...」では一貫して「ジェネリックであること」に重きを置いています.** プログラミング言語一般における`else`の意味を損なうような用法は避けるべきだという主張は, 一通り読み終えた今, 当然のことのように感じます. こういう思考が身につくことに「Effective...」を読む意義があると思います. 

## クロージャとはこれ如何に？
クロージャとは状態を保持する仕組みです, と言ってもよくわからないので例をば:

```py3
def closure():
    flag = False

    def getter():
        print(flag)
    return getter


func = closure()
func() # False
```
`func()`は`flag`という変数を保持していることになります. このテクニックを用いるとグローバル変数を減らすことができたりと便利です. 

注意しなければならないことは代入が絡むと悲しいことが起きることです:

```py3
def closure():
    flag = False

    def setter():
        flag = True
    setter()
    return flag


print(closure()) # False
```
`setter()`で`flag = True`を試みていますが, `closure()`の戻り値は`False`です. これは`setter()`内のスコープには`flag`が存在しないために代入が変数定義として扱われてしまったのです. クロージャを活用したいときは代入にはよくよく注意しなければなりません. この問題を解決するためには`nonlocal`を用います:

```py3
def closure():
    flag = False

    def setter():
        nonlocal flag
        flag = True
    setter()
    return flag


print(closure()) # True
```
これで一つ上のスコープを参照してくれるようになります. この問題は, Pythonでは変数定義と代入が同じ文法であるということが根源です. これを解決するために変数定義をJavascriptのように`var flag = False`とすることも提案されたようですが, 互換性のことも考えて`nonlocal`を採用したようです. 

クロージャは便利ですが`nonlocal`を大きい関数で使うと把握が難しくなるので, そういうときはクラスを使ったほうが良いそうです. 


## キーワード引数の位置に注意
細かいことですが, キーワード引数は位置引数の後ろで指定しなければなりません:

```py3
def remainder(number, divisor):
    return number % divisor


remainder(20, 7)                # OK
remainder(20, divisor=7)        # OK
remainder(number=20, divisor=7) # OK
remainder(divisor=7, number=20) # OK
remainder(number=20, 7)         # NG
```

## 辞書を入れ子にするべからず！クラスを使うべし
辞書はコンテナとして非常に優秀ですが, 辞書の中に辞書が入っているような入れ子構造にすると保守が非常に辛くなります. そうなる前にクラスに分割しましょう:

```py3
import collections
Grade = collections.namedtuple('Grade', ('score', 'weight'))


class Subject(object):
    def __init__(self):
        self._grades = []

    def report_grade(self, score, weight):
        self._grades.append(Grade(score, weight))

    def average_grade(self):
        total, total_weight = 0, 0
        for grade in self._grades:
            total += grade.score * grade.weight
            total_weight += grade.weight
        return total / total_weight


class Student(object):
    def __init__(self):
        self._subjects = {}

    def subject(self, name):
        if name not in self._subjects:
            self._subjects[name] = Subject()
        return self._subjects[name]

    def average_grade(self):
        total, count = 0, 0
        for subject in self._subjects.values():
            total += subject.average_grade()
            count += 1
        return total / count


class Gradebook(object):
    def __init__(self):
        self._students = {}

    def student(self, name):
        if name not in self._students:
            self._students[name] = Student()
        return self._students[name]


book = Gradebook()
albert = book.student('Albert Einstein')
math = albert.subject('Math')
math.report_grade(80, 0.10)
math.report_grade(80, 0.10)
math.report_grade(70, 0.80)
gym = albert.subject('Gym')
gym.report_grade(100, 0.40)
gym.report_grade(85, 0.60)
print(albert.average_grade())
```
ちょい長いですが, 頑張って読んでみましょう. **「保守・拡張が容易なクラス設計とは如何なるものか」という問いに対するひとつの答えになっているように思えます.** 

「1.成績表 → 2.生徒 → 3.科目 → 4.点数」という層構造を, 各オブジェクトのインスタンス変数(辞書)が下の階層のオブジェクトを保持することで実現しています. 各クラスはインスタンス変数に下位クラスのインスタンスをセットするためのメソッドを持ち, さらに平均点を計算するメソッドもあります. じっくり眺めると, このコードが高い拡張性を持つことがわかるでしょう. もしこれを単一の辞書で実現しようとするとヤバいです. 

上のコードは直感的にすぐ理解できるかどうかはわかりません. 自分はすぐにはわかりませんでした. しかしわかってしまえばとてもシンプルなアイデアであることが納得できました. 一方で, 理解できてなお「なんでこんな面倒くさい実装を！？」と感じるコードもあります. **理解が容易なコードが良いコードだとは限りません. 説明が容易なコードが良いコードなのです(多分).** Zen of Pythonでもそんなことを言ってます:

>   Although that way may not be obvious at first unless you're Dutch.
> そのやり方は一目見ただけではわかりにくいかもしれない。オランダ人にだけわかりやすいなんてこともあるかもしれない。
> 
> If the implementation is hard to explain, it's a bad idea.
> コードの内容を説明するのが難しいのなら、それは悪い実装である。
> 
> If the implementation is easy to explain, it may be a good idea.
> コードの内容を容易に説明できるのなら、おそらくそれはよい実装である。

from [プログラマが持つべき心構え (The Zen of Python)](http://qiita.com/IshitaTakeshi/items/e4145921c8dbf7ba57ef)

## `@classmethod`ってどこで使うの？

自身のオブジェクトを生成するメソッドに使うとよいみたいです. 以下のような親クラス・子クラスを考えます:

```py3
from tempfile import TemporaryDirectory

class GenericInputData(object):
    def read(self):
        raise NotImplementedError

    @classmethod
    def generate_inputs(cls, config):
        raise NotImplementedError


class PathInputData(GenericInputData):
    def __init__(self, path):
        super().__init__()
        self.path = path

    def read(self):
        return open(self.path).read()

    @classmethod
    def generate_inputs(cls, config):
        data_dir = config['data_dir']
        for name in os.listdir(data_dir):
            yield cls(os.path.join(data_dir, name))


class GenericWorker(object):
    def __init__(self, input_data):
        self.input_data = input_data
        self.result = None

    def map(self):
        raise NotImplementedError

    def reduce(self, other):
        raise NotImplementedError

    @classmethod
    def create_workers(cls, input_class, config):
        workers = []
        for input_data in input_class.generate_inputs(config): # 引数に取ったクラスのインスタンスをつくる
            workers.append(cls(input_data)) # 自身のインスタンスをつくる
        return workers # 自身のインスタンスを返す


class LineCountWorker(GenericWorker):
    def map(self):
        data = self.input_data.read()
        self.result = data.count('\n')

    def reduce(self, other):
        self.result += other.result  


with TemporaryDirectory() as tmpdir:
    config = {'data_dir': tmpdir}
    workers = LineCountWorker.create_workers(PathInputData, config)
```
これも長いですが, がんばって！ 重要なポイントは2点あります. 

一つめはインスタンスを作る責任をそのクラス自身のクラスメソッドが担っているということです. **新しい子クラスたちがさらに追加されると「そのクラスのインスタンスはどこで誰が作るのか」を管理するのが大変になります.** 上の例では`with`以下で`LineCountWorker.create_workers()`が自身のインスタンスを作っています. さらにその中で `PathInputData` のインスタンスも作っています[^2]. つまり「どこで」「誰がつくるか」が明確になっているのです.

二つめは一つめと密接に関わっています. それは「`cls`は呼び出されるクラスに置き換えられる」ということです. まあ当たり前のことに思えますが, とても重要です. 上のコードであれば, **`GenericWorker.create_workers()`内で定義された`cls(input_data)`は, その子クラスである`LineCountWorker`を通して呼ばれると`LineCountWorker(input_data)`に化けるということです.** このおかげで, `GenericWorker`を親クラスとする派生クラス`HogeCountWorker`を新たに作ったとしても, そのインスタンスを作るのはやはり`HogeCountWorker.create_workers()`であり, `HogeCountWorker`内で新たに`create_workers`メソッドを作る必要は無いわけです. 親クラスから継承したメソッドなのに, 派生クラスから呼び出すとその子専用のメソッドに早変わり！ これが「Effective...」内で散々語られている「ジェネリック」ということです. 


# 販促, 再び.
他にもイテレータ・ジェネレータ関連のtipsや`@property`の使いどころなど, 為になるいいことがたくさん書いてあります. 是非是非買って読んでみてください[^3].


# あまり良くないところ
Effective Pythonは素晴らしい本ですが気になるところもあります. **翻訳の質です.** 例えば...

|例1||
|------|-------------------------------------------|
| (原) | You'd want a similar abstract interface for the MapReduce worker that consumes the input data in a standard way. |
| (訳) | 入力データを標準的に消費するMapReduceのWorkerにも同様の抽象インターフェースが欲しくなったとしましょう。|

|例2||
|------|-------------------------------------------|
| (原) | Here, I extend the InputData class with a generic class method that's responsible for creating new InputData instances using a common interface. |
| (訳) | InputDataクラスを拡張して、共通のインターフェースを用いる、新たなInputDataインスタンスを作る責任を負う、ジェネリックなクラスメソッドを追加します。 |

うーん...微妙です. 本当に内容を理解して書いているのかが疑わしい箇所も多く感じました. 特に「3章 : クラスと継承」は辛かったです. 自分の理解力不足を翻訳のせいにしたくなる程でしたが...いや, まあ, 自分の頭が足りないのが悪いのです.

とはいえ内容そのものが素晴らしいことに変わりはありません. 英語が得意な人は原文で読むことをおすすめします.


# おわりに
プログラミングに限らず, 勉強本で最も重要なのは「優れた入門書」だと思っています. 数学を諦める高校生やC言語で脱落する大学生を見るにつけ, 初心者と初級者の間にある壁が高いことは明らかですから.

有り難いことにPythonは入門書の数には事欠きません. 母数が多ければその中に優れた本もあることでしょう. 一方で初級者・中級者向けの和書はどうでしょうか？ オライリーからPython本はたくさん出ていますが, 幅広い層をカバーする脱初級者本は「Effective Python」と「実践 Python3」, オライリーを除けば「エキスパートPythonプログラミング」くらいなのではないでしょうか[^4]. その中のひとつである「Effective Python」がこの完成度をもって上梓されたことは本当に喜ばしいことです.

# 参考図書
- Brett Slatkin, Effective Python(オライリー・ジャパン, 2016)
- Brett Slatkin, Effective Python(O'Reilly, 2015)
- 西尾泰和, コーディングを支える技術(技術評論社, 2013)

[^1]: This software includes the work that is distributed in the Apache License 2.0
[^2]: 正確には `PathInputData` のインスタンスを, 自身である `LineCountWorker` のコンストラクタに渡している.
[^3]: オライリーの回し者ではありません.
[^4]: もっといろいろあったらすいません.