Effective Pythonの第二版(Effective Python: 90 Specific Ways to Write Better Python)を読んだので、簡単な感想とメモを残しておく。
- 作者:Brett Slatkin
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2019/11/25
- メディア: ペーパーバック
感想
業務でシミュレーションするときはPythonを使っているのだけれど、 もっと良い書き方がないかと思ってEffective Pythonの第二版(洋書)を読んだ。 第二版は2019月11日発売。Python3.8に対応している。 平易な英語で書かれているし、洋書でたまに見かけるつまらないジョークもないので非常に読みやすい。 コードがメインの本であることが、読みやすさに寄与しているのかもしれないが。
この本は、コードを通して「こういう書き方をするとわかりやすくかけるよ」と伝える内容になっている。 便利な標準ライブラリの機能について勉強でき、非常にためになった。 もっとPythonを使いこなしたいという人にオススメしたい。 ただ、私が非同期処理に詳しくないせいか、非同期の章はわかりづらく感じた。 asyncioを使えばノンブロッキング処理ができることはわかったが、書けるようになったかというと疑問が残る。
メモ
以下、参考になった部分のメモ。 自分用メモなので、本を読んでない人が見ても内容がわからないかもしれないが、ご容赦を。
Chapter1
Item 6
a, b = b, aみたいにswapができる
Chapter2
Item14
- ソート:タプルをソートすると、最初の要素でまずソートされ、次に2番目の要素でソートされる。
- ソートで昇順、降順を色々変更したい場合には、ソートしたい順番の逆順に順々にソートする。
Item 16-18
- リスト
aをb=aとやると参照になる。スライスは値わたし(コピー)。a[-0:]はリスト全体を渡すので参照になる。 a[2:7] = [99, 22]とかでもいい感じにやってくれる。- dict クラスは
get, setdefault, defaultdict, __missing__あたりが有用。 setdefault: キーが存在してもデフォルト値の計算が毎回発生。defaultdict: デフォルト値を指定できる。デフォルト値生成用関数に引数は取れない。__missing__:defaultdictで、デフォルト値生成用関数に引数を取りたいときに使う。
Chapter3
Item19
- 戻り値が4つ以上のタプルになったら、
named tupleを使ったほうがよい。→Qiita情報によればtyping.NamedTupleの方がよいかも。あるいはdataclasses。
Item20
- 戻り値に
Noneを使うよりは例外を出したほうが良い
Item21
- closure内で値を代入するときは注意。
nonlocalキーワードを使わないと、スコープ外の変数にアクセスできない。ただし、nonlocalを使いたい場合はクラスに__call__を実装した方が簡潔にかける。
Item24
{}, [], datetime.now()など、ダイナミックな値を引数の初期値にしたいときは注意。関数の定義時に1回だけ評価されるため。[]の場合はすべての呼び出しから参照されるので面倒なバグになる。
Item25
- 関数の引数を宣言するときに
*をつかうと以後のパラメータはkeyword-only parametersになるPositional-Only Parameters/(3.8から)もある
Item26
- デコレータを使うと関数のメタデータが変わる。functoolsの
wrapを使うとこれを防げる。
Chapter4
Item28
- リスト内包表記は2 control subexpression(ループ、条件のこと)まで
- 1ループ+1条件 or 2条件 or 1条件 + 1ループ
Item29
- セイウチ演算子を、リスト内包表記の条件部以外で使うと変数がリークする(他の場所でも使えるようになる)ので避けるべき
Item30
- リストを作ってそれを返すような関数を書くよりも、ジェネレータで書いた方がシンプルになることが多い
Item31
- 引数がイテレータとなり得て、かつ、関数内で複数イテレートする場合は、イテレータを最初に使い切るので挙動がおかしくなる。
- 上記の場合は、
__iter__メソッドに「ジェネレータ」を実装したコンテナクラスを用いるべき。そのときは型チェックを忘れずに。 - イテレータ:クラスの属性、ジェネレータ:メソッドの属性なので、イテレータの
__iter__メソッドがジェネレータなのは問題ない。ジェネレータなので、__next__()も実装されている。
Item32
- リスト内包表記のような記法で「ジェネレータ式」がある。メモリ使用量が削減できるので、大きなデータを扱う場合には便利。
Item33
- ネストされたジェネレータを使う時は
yield fromを使うと簡潔にかける。しかも速い。
Item34
sendを使うとyieldに式をわたせるが、これを使うよりもジェネレータを引数として与える方が動作がわかりやすい。戻り値がNoneになったりしない
Item35
- generator内で
throwを呼ぶと例外を送れるが、可読性が低くなるので使わない方がよい。 __iter__にジェネレータを実装したクラスを使った方がよい。
Chapter 5
Item37
@classmethodをコンストラクタの代替のように使うことができ、抽象度を高められる。namedtupleは初期値を設定できない。設定したい場合はdataclassesを使うと良い。
Item38
__call__をクラスに実装すると、クラスを関数のように使える
Item39
@classmethodを使うと、引数が違うコンストラクタのようなものを定義することができ、抽象度を高められる。
Chapter 6
Item 46
weakref(WeakKeyDictionaryなど) を使うと、dictionaryが或るオブジェクトへの最後の参照になったときに自動的にメモリが解放される
Item 47
__getattr__,__setattr__を使うとlazy_load, lazy_saveができる__getattr_はそのattribute(インスタンス変数など)がないとき、一度だけ呼び出される。__getatribute__は毎回呼び出される
Item48
__init_subclass_を使うと、継承先クラスのattributeに対して簡単にvalidationできる- Metaclassでもできるが、冗長な書き方になることが多い。
Item50
@propertyをもっと一般したもの→ descriptor。__get__,__set__で、getter, setterの動作をカスタマイズできる__set_name__は各descriptorに対し、クラス生成時に一度だけ呼ばれる、クラス名と割り当てられた名前を得られる。
Item 51
- クラスデコレータは、クラス中の全てのメソッドやattributeをいじりたい場合に便利
Item 57
- threadの中で例外が起きても、それを拾うことはできない
Item 59
- マルチスレッド:ThreadPoolExecutor
Item 60
- ブロッキング処理:asyncio
Item 64
- multiprocessingを使う前にProcessPoolExecutorの使用を考えたほうが良い。外部ライブラリをいれてもいいなら、joblibでもよいかも。
Chapter 8
Item 65
- 例外処理の
elseは、tryの中のプログラムが正常に動作した場合のみ実行される
Item 66
contextmanagerデコレータを使うと、withを使う関数を簡単に作れる
Item 71
- listの
pop()は遅い。collection dequeのpopleft()を使った方が良い(リスト長に対して処理時間が線形)
Item 73
- 優先度付きキュー:heapq(最小値 or 最大値をとりだすのがはやい)
Item 74
- numpyのviewみたいな、参照だけ持ってこれるやつ→memoryview。スライスが参照になる。
- bytearray→bytesのmutableバージョン。これをmemoryviewでwrapすると、mutableなスライスができる。
Chapter 9
Item 76
- デバッグの時は
repr()でターミナルに出力したほうが型がわかりやすい。f-stringなら!rを指定する。
Item 81
tracemallocを使うと、大量にメモリを使っている場所がわかる
Chapter 10
Item 87
- 自作モジュールでは、Exceptionを継承した、自作の例外クラスを使うと良い