Effective Python 3版(英語版) の読書メモ
Effective Pythonについて
Effective PythonはPythonのより効果的な書き方についてのヒントをまとめた書籍です。
これはEffective Software DevelopmentシリーズのPython版となります。
2025年8月時点での最新は3版となり、3.13までを網羅しています。
版数 | 発売日 | Pythonのバージョン | 項目数 |
---|---|---|---|
1版 | 2016/1/23 | 3.4 | 59 |
2版 | 2020/7/16 | 3.8 | 90 |
3版(英語のみ) | 2024/11/30 | 3.13 | 125 |
基本的に各章の項目のどこから読み始めても理解できるような書き方になっているので、自分に興味のあるところから読み進めることが可能になっています。
ただ、全くの初心者向けの本ではないです。「Python的な最適な流儀はよくわからないが、文法的に正しいものは書ける」あたりの人が対象になっているかと思います。
すくなくとも最初に読むPythonの本ではないはずです。
今回はEffective Python 3rdを読んだ時の、各章のメモを記述しましたので、本書の購入を検討なさっている方の参考になれば幸いです。
なお、2025/10/16に日本語版が発売予定なので、それを待ったほうが良いかもしれません。
Chapter 1 Pythonic Thinking
Pythonで最も一般的なことを行うための手法が書かれています。
「Pythonic」という言葉は出しているが、別にそれを解説するようなことはないので、前述の通り、本書から入門を始めるのは適切ではないと思います。
この章の内容は以下のようになります。
- 使っているPythonのバージョンの確認方法がかいてある
- スタイルのガイドラインの説明。自動フォーマッタやチェックツールの紹介もある
- コンパイル時にエラーは検出できないことを期待してはいけないことが書いてある
- 1行で複雑な式は書けるけど、ヘルパー関数にしたほうがいい
- 単一のステートメントで複数の値を割り当てるアンパックを使用してインデックスの使用を回避できる
-
1,
より(1,)
で宣言した方がいい理由について - 条件式の使用について
- 代入式の使用について
- matchをどういう時に使用するか
Chapter 2 Strings and Slicing
文字列・バイト列とスライス/アンパックのベストプラクティスが記載してあります。
この章の内容は以下のようになります。
- bytes型とstr型の違い
- f-stringを優先して使った方がいい理由
- オブジェクトのprint時のreprとstrの違い
- 隣接する文字列リテラルの暗黙結合は避け、明示的に連結したほうがいい理由
- 文字列やリストに対するSliceの方法と避けた方がいい書き方
- アスタリスク付きのアンパッキングを使う例
Chapter 3 Loops and Iterators
ループとイテレータのベストプラクティスについて記載しています。
この章の内容は以下のようになります。
- rangeを使ってインデックスを取得するよりenumerateを使用した方がいい
- zipで同時にイテレータを処理できる
- ループの後にelseは文法的につけられるが直感的じゃないから避けた方がいい
- ループで使った変数をループ後の処理で使うのはやめといた方がいい
- 入力引数にイテレータがあって複数回繰り返し実行する場合は予期せぬ挙動をするときがあるので気をつける。
- 反復中にコンテナのアイテムを直接修正するのはやめた方がいい
- any/all組み込み関数を活用する
- itertools関数の使用を検討する
Chapter 4 Dictionaries
辞書型のベストプラクティスについて記載しています。
この章の内容は以下のようになります。
- dict型は3.7からは挿入順に反復されるようになっている。しかし辞書のようなカスタムクラスの場合、その挿入順の保持は想定できないので注意する
- dict.getをinやKeyErrorより優先して使用を検討した方がいい理由
- 欠損要素を扱うときは、setdefault よりも defaultdict を優先した方がいい理由
- dict型を継承したサブクラスでは
__missing__
メソッドを作成することでデフォルト値を提供できる - 複雑なネストのdictやlist, tupleを作るならクラスの利用を検討する
Chapter 5 Functions
関数のベストプラクティスについて記載しています。
この章の内容は以下のようになります。
- “オブジェクトへの参照”が引数に渡る。渡されたオブジェクトの属性は関数内で変更され得るので注意
- 4つ以上の値をreturnして呼び出し元でアンパッキングするより、データクラスの使用を検証する
- Noneを返すよりは例外をだすことを検討する
- クロージャー中の変数スコープとnonlocalを使用した時の影響
- 可変個の位置引数を使用することで余計な記述を減らして可読性を保てる。
- デフォルト値をもつキーワード引数を使用することで、既存の呼び出し元を全て修正することなしに新しい動作を追加できる
- デフォルト引数をNoneにして動的に引数の内容を変更する必要がある場合は、Docstringsに記載しておく
- /や*などの特殊な記号を使用することで呼び出し元にたいして位置引数のみやキーワード引数のみにすることができる
- functools.wrapsを使用して関数デコレータを定義できる
- ラッパー関数を定義するときはfunctools.partialの使用を検討する
Chapter 6 Comprehensions and Generators
内包表記とジェネレータのベストプラクティスについて記述してあります。
この章の内容は以下のようになります。
- mapやfilterの代わりに内包表記で記述することを検討する
- 複雑すぎる内包表記を書かないようにする
- 代入式を使用すると内包表記中のコピペを減らせる
- リストを返すよりジェネレータを返すことを検討する
- リストの内包表記は大量のメモリを使用するケースがあるので、その際はジェネレータ式を使用することを検討する
- 複数のジェネレータをyield fromでまとめることができる
- sendを使うより、イテレーターを引数として渡すべきかを検討する
- 状態遷移が必要な反復処理はジェネレータにたいして例外で制御するのでなく、クラスで管理することを検討する
Chapter 7 Classes and Interfaces
クラスとインターフェイスのベストプラクティスについて記述してあります。
オブジェクト指向を意識せずにPythonを使用しているケースもあるので、そういう場合は優先度が低くなる章です。
この章の内容は以下のようになります。
- 組み込み関数の多くは関数を渡すことで動作をカスタマイズできるので、シンプルなインターフェイスの場合は関数を使用するようにしておく
- isinstanceを使って型による挙動を変えるよりオブジェクト指向の多態性を使うことを優先する
- メソッドの多態性の代わりにfunctools.singledispatchの使用を検討する
- @dataclassで軽量のクラスを定義できる
- pythonは
__init__
が1つしかないが、@classmethodを使用することで代替のコンストラクターを定義することができる - 親クラスを初期化するときはsuperを使用しておく
- 少数のメソッドだけを提供して他のクラスに機能を提供するだけのミックスインクラスの使用を検討する
- PythonのPrivate属性は厳密には強制されないので注意
- 独自の不変オブジェクトを作るときは
@dataclass(frozen=True)
でdataclassを作る - カスタムのコンテナクラスを作る時はcollections.abcから継承する
Chapter 8 Metaclasses and Attributes
属性とメタクラスのベストプラクティスについて記述してあります。
この章の内容は以下のようになります。
- セッター、ゲッターメソッドを作るよりは、そのまま属性を利用する
- 属性アクセス時に特別な動作が必要になったら@propertyを利用する
- 属性の型変更などのリファクタリングの代わりに@propertyを検討する
- 属性のロジックの再利用が必要なケースではdescriptorsの使用を検討する
- オブジェクトの属性を遅延的に取得と設定するには
__getattr__
,__getattribute__
,__setattr__
を使う。 -
__init_subclass__
でサブクラスが定義された時点で、そのサブクラスが適切な形であるかを検証できる -
__init_subclass__
を使用することで基本クラスのサブクラス化がされるたびに任意の登録コードを実行できる -
__set_name__
を使用することで“左辺の名前”をそのまま属性の設定に使うことができる。 - Pythonではクラス本体で定義した属性の順序を保持する
- クラスの拡張にはクラスデコレーターを優先させる
Chapter 9 Concurrency and Parallelism
まず、この章の前提として並行性と並列性について明確に区別する必要があります。
並行性は動作するプログラムを瞬時に切り替えることで、あたかも複数のプログラムが同時に実行されているように見せかけます。
一方、並列性は実際に複数のCPUで複数のプログラムを同時に実行しています。
Pythonはスレッドや非同期コルーチンで並行に実行させることは容易です。しかし、これを真に並列実行させるのは困難になっています。
この章ではPythonの並行性と並列性に関するベストプラクティスが記述してあります。
パフォーマンスなどが気にならないケースにおいては優先度が低くなる章です。
この章の内容は以下のようになります。
- 子プロセスの管理にはsubprocessを使う
- CPUバウンドの並列化では標準のPythonスレッドはGILのため有効でない。ただしI/OバウンドやGILを解放するC拡張(NumPy等)ではスレッドが効く場合もある
- GILはPython3.13以降実験的に無効にできる
- GILがあってもスレッドで使うデータの排他制御は必要
- スレッド間の協調作業にキューを使用する
- どういうときに並行性が必要になるかを理解する
- スレッドを生成し、そのタスクが終わったらスレッドを破棄するような作りは避ける
- キューを使用した並行性の実現にはリファクタリングが必要になる
- スレッドが必要な並行処理では ThreadPoolExecutor を検討する
- コルーチンを使うと高い同時実行性をもつI/Oを実現できる
- スレッドによるI/Oをasyncioを使用したコルーチンに移植する方法
- スレッドとコルーチンを併用して asyncio への移行を容易にする
- 非同期対応ワーカースレッドでは非同期イベントループの応答性を最大化する
- asyncio.runのdebug=Trueで遅いコールバックやI/Oセレクタの遅延をログに出してボトルネック検出に役立つ
- 真の並列処理をしたい場合はconcurrent.futuresの利用を検討する
Chapter 10 Robustness
堅牢性に関するベストプラクティスについて記載してあります。
場当たり的なプログラムではなく、長期にわたってメンテナンスをする必要があるプログラムを書く場合は目を通した方が望ましいでしょう。
この章の内容は以下のようになります。
- try, except, else, finallyを活用する
- assertでプログラマーの想定を書いておく。
- 例外的な状況ではraiseで報告する
- contextlibとwithステートメントでtry/finallyの再利用を検討する
- tryブロックはなるべく短く
- Exceptionブロックで割り当てた例外の変数の生存期間に注意
- Exceptionでcatchするときの注意
- ExceptionとBaseExceptionの違い
- tracebackを使って例外の報告を拡張する
- トレースバックを明確にするため、例外を明示的に連鎖させることを検討する
- リソースを適切に割り当ててクリーンアップするためにジェネレーター関数を使用するのではなく、リソース (ファイルやミューテックスなど) をジェネレーター関数に渡す方が適切な場合が多い
-
__debug__
がFalseになるような設定にしない(したところでパフォーマンスの優位性は限られる) - 開発者ツール以外でexecやevalの使用は避ける
Chapter 11 Performance
パフォーマンスに関するベストプラクティスが記述してあります。
パフォーマンスが問題になったとき、あるいはパフォーマンスが重要なコードを書く必要がある場合は目を通した方がいいでしょう。
なお、パフォーマンスが最も重要な課題である場合はHigh Performance Python: Practical Performant Programming for Humans 3rd Editionを読んだ方が良いかもしれません。
この章の内容は以下のようになります。
- 最適化をする前にプロファイルを行うべき
- timeitでマイクロベンチマークを行いどこを最適化すればいいか検討できる
- どういうケースで別のプログラミング言語に置き換えたほうがいいか
- ctypesを使用することでネイティブのライブラリを簡単に結合できる
- C 拡張モジュールの使用の検討
- あらかじめバイトコードを生成しておくと起動がはやくなる
- import時の遅い読み込み時間の削減方法
- memoryviewを使用するとバッファをゼロコピーで読み書きできる
Chapter 12 Data Structures and Algorithms
データ構造とアルゴリズム関連のベストプラクティスをまとめた章です。
この章の内容は以下のようになります。
- sortメソッドのkeyパラメータを使って複雑な条件のソートができる
- sortメソッドとsortedの違いを理解する
- ソート済みの値を検索する場合はbisectの使用を検討する
- キューを作る場合はdequeの使用を優先する
- なんらかの優先順位が必要なキューの場合、heapqの使用を考える
- 異なるタイムゾーンがあるケースでは時刻はUTCで扱い、最後の表示のステップで現地時間の変換を行う
- 精度が重要ならdecimalを利用する
- pickleでシリアル化する場合、下位互換を確保するのにcopyregを使用する
Chapter 13 Testing and Debugging
単体テストのベストプラクティスやデバッグの方法について記述があります。
実験コードではなく製品コードを書く必要がある場合は目を通しておいた方が望ましいです。
この章の内容は以下のようになります。
- TestCaseクラスでテストを書く
- Python の動的性質ゆえ、統合テストを厚めにした方がよい
- setUp, tearDown, setUpModule, tearDownModuleを使用してテストを分離する
- 複雑な依存をモックで代用する
- 依存関係をカプセル化してモックとテストを容易にする
- 浮動小数点の結果をテストするときはassertAlmostEqualを使用する
- pdbを使用して対話的にデバッグすることを検討する
- tracemallocでメモリ割り当ての追跡を行いメモリの使用量を理解する
Chapter 14 Collaboration
複数人における協働作業を行う場合のベストプラクティスについて記述してあります。
チームでの作業が前提であるなら目を通しておいたほうが望ましいです。
この章の内容は以下のようになります。
- コミュニティが作成したモジュールをどこから見つけるかを知る
- 独立して再現可能な環境のために仮想環境を利用する
- 関数やクラス、モジュールにはドキュメンテーション文字列を書くようにする
- パッケージを使って安定したAPIの提供とモジュールの組織化をする
- プログラムが実行される環境を構成するための方法を検討する
- 公開APIを作るなら、そのモジュール専用の規定の例外を定義し、以後は必ずそれを継承した例外だけを外に出すようにする
- 循環依存の解決方法
- warningsモジュールで非推奨の使用方法を通知する
- mypyなどの型チェックをする静的解析の導入を検討する
- 配布のために zip に固めて実行(zipimport / zipapp) は原則やめてPexなどの使用を検討する
全体を通しての感想
Pythonは多くのバージョンアップをしているため、古くからつかっていたとしても意外と知らない機能があったりします。
現時点での一般的な書き方を知るという意味では有用な書籍になっています。
ただし、ある程度のことは知っていることが前提になっているため、この書籍でPythonを入門するというのはやめておいたほうが無難であると思います。
Discussion