Contents
スポンサーリンク
概要
「エキスパートPythonプログラミング 改訂3版」を読んだので感想を書こうと思います。
似たような書籍で「きれいなPythonプログラミング」の感想も書いていますのであわせてご覧ください。
技術書のセールとおすすめ書籍を紹介しています。合わせてご覧ください。
スポンサーリンク
書籍概要
どんな本?
卓越したPythonプログラマになるための必読書
Pythonプログラマ必読と言われるベストセラーが、最新のPython環境に合わせて改訂されました。
本書は、Pythonを使って仕事をしている開発者が普段どのようなツールやテクニックを用いて仕事をしているのか、また開発者が実際に現場で用いているベストプラクティスについて解説した書籍です。
本書を読むことで、先進的なPythonプログラマが日常的に使用している開発ノウハウを学ぶことができます。
今回の改訂によって、新たにメタプログラミング、イベント駆動型プログラミング、型ヒントについての解説が追加されました。
- 著 Michal Jaworski、著 Tarek Ziade、訳 稲田 直哉、訳 芝田 将、訳 渋川 よしき、訳 清水川 貴之、訳 森本 哲也
- 2021年07月 発行
- 616ページ
- 定価4,180円(税込)
スポンサーリンク
内容のまとめと感想
コード、環境、テストなど様々な観点でPythonに関してのベストプラクティスをまとめている本です。
かなり本格的な内容が多く、以前紹介した「きれいなPythonプログラミング」と一部重複があるものの、さらにディープな内容まで取り上げています。
特徴
Pythonの中級者向け
Pythonの入門者ではないので、基本的な文法等に関する説明はありません。
ある程度Pythonを使いこなしている人がさらなる飛躍を目指すために読むための本になっています。
広さと深さを兼ね備えた内容
ページ数が600を超えているだけあって、非常にボリュームたっぷりの書籍です。
開発環境、コードの書き方のベストプラクティス、メタプログラミング、Linter、他言語Python拡張、CI/CD、ドキュメント(docstring)、ユニットテスト、性能の最適化、平行処理、デザインパターンと非常に盛り沢山の内容になっています。
この1冊でPythonの知るべきテクニックやプラクティスをかなり学ぶことができます。
翻訳者の補足が優秀
内容によっては翻訳者の補足といった項目が設けられていて、わかりにくい部分の追加説明や、さらに便利な内容の紹介などの補足が行われていて素晴らしいです。
まとめ
かなり盛り沢山の内容で、この1冊を読み込むことでPythonのスキルを向上させることができると思います。
ある程度Pythonを書けるようになったエンジニアが1冊は持っておくべき良書だと思いました。
かなりボリュームもあってヘビーなので、肩慣らしとして内容が似ている「きれいなPythonプログラミング」を先に読んでから取り組むと理解が進むかもしれません。
とりあえず2周ほど読み直しましたが、まだまだ完全に理解できているレベルではないので定期的に読み直したいと思いました。
ペースは遅いですが、山のように積読になってしまっているPython関係の書籍を少しづずつですが消化しています。
次は下記の2冊あたりを読みたいと思っています。
個人的なメモ
構文ベストプラクティス
連結
zip関数を使用することで、list要素の結合ができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 2つのlistを結合 for items in zip([1,2,3], [4,5,6]): print(items) # (1,4) (2,5) (3,6) # zipに再度zipで元に戻る for items in zip(*zip(1,2,3), [4,5,6]): print(items) # (1,2,3) (4,5,6) # 要素数が一致しない場合には長い方は切り捨てられる for items in zip([1,2], [3,4,5,6]): print(items) # (1,3) (2,4) |
変数のアンパック
変数の格納に対して下記のような形で配列等をアンパックして格納できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 変数のアンパッキング first, second, third = "1", "2", "3" # スター式でシーケンスの残りを全て格納する first, second, *rest = 1, 2, 3, 4 # rest = [3, 4] # 中間で使用も可能 first, *innter, second = 1, 2, 3, 4 # rest = [2, 3] # second = 4 # ネストされたシーケンスのアンパック (a, b), (c, d) = (1, 2), (3, 4) # a=1, b=2, c=3, d=4 |
Enum
enumモジュールのEnumクラスを使用して列挙型を定義できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from enum import Enum, auto # 手動で割り振り class WeekDay(Enum): MONDAY = 0 TUESDAY = 1 WEDNESDAY = 2 # ... # 自動で割り振り class Status(Enum): PENDING = auto() PROCESSING = auto() PROCESSED = auto() # example def test(day): if day == WeekDay.MONDAY: #... |
イテレーターのアンパック
*をつけてイテレータをアンパックする方法を応用して、リスト同時の結合ができる
1 2 3 4 5 |
a = [1, 2, 3] b = [4, 5, 6] [*a, *b] # [1,2,3,4,5,6] |
ジェネレータとyield文
yield文を使用してジェネレータを作成できる。
無限配列を実現できて、下記のようなフィボナッチ数を呼び出しの度に返すような仕組みが作れる。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def fibnonacci(): a, b = 0, 1 while True: yield b a, b = b, a + b fib = fibnonacci() next(fib) # 1 next(fib) # 1 next(fib) # 3 |
関数デコレーター
関数内に関数を返すことでデコレーターとして関数をラップした機能を作れる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def mydecorator(funcion): # ラッパー関数 def wrapped(*args, **kwargs): # 関数呼び出し前に実行したい処理をここに書く print("実行前") result = funcion(*args, **kwargs) # 関数呼び出し後に実行したい処理をここに書く print(f"実行後:{result}") return result # ラッパー関数を返す return wrapped @mydecorator def test(): return 5 test() # 実行前 # 実行後:5 |
クラスベースでの書き方も可能。
1 2 3 4 5 6 7 8 9 10 11 |
class DecoratorClass: def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): # 関数呼び出し前に実行したい処理をここに書く print("実行前") result = self.function(*args, **kwargs) # 関数呼び出し後に実行したい処理をここに書く print(f"実行後:{result}") return result |
さらにラップすることで引数を取れるデコレーターを作れる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 引数を持ったデコレーター def repeat(number=3): def actual_decorator(function): # ラッパー関数 def wrapped(*args, **kwargs): result = None for _ in range(number): result = function(*args, **kwargs) return result return wrapped return actual_decorator @repeat(3) def print_test(): print("test") |
クラスデコレーター
引数にクラスを受け取って関数デコレーターを作ることも可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# クラスデコレーター def short_repr(max_width=8): # クラスをラップするための関数 def parameterized(cls): # 元のクラスを継承して新しいクラスを生成する class ShortRepr(cls): def __repr__(self): return super().__repr__()[:max_width] return ShortRepr return parameterized @short_repr(3) class VeryVeryVeryLongName: def __repr__(self): return "VeryVeryVeryLongName" test = VeryVeryVeryLongName() print(test) # Ver |
ただし、この場合クラスを継承して別のクラスとなってしまうため、クラス情報などのメタ情報が変わってしまうという問題がある。
Pythonの場合、ミックスインによる多重継承が可能なのでそちらを使用した方が手軽で良い。
データクラスを用いたボイラープレートの削除
dataclassデコレーターを使用すると、初期化や文字列の出力処理などを代わりに実行してくれるようになり、提携的なコードの記述量が少なくできる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# ボイラープレートを使用しない場合 class Vector: def __init__(self, x, y): self.x = x self.y = y # 文字列の出力 def __repr__(self): return f"<Vector: x={self.x}, y={self.y}>" # 一致比較 def __eq__(self, other): return self.x == other.x and self.y == other.y from dataclasses import dataclass # dataclassデコレーターを使用すると上の3つの処理を自動でやってくれる @dataclass class VerctorC: x: int y: int |
ユニットテスト
標準で用意されたunittestモジュールを使用することでxUnitライクなユニットテストを記載できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import unittest # unittest.TestCaseを継承する class SumTests(unittest.TestCase): def test_sum_positive(self): self.assertEqual(5, sum(2,3)) def test_sum_negative(self): self.assertEqual(-1, sum(2,-3)) # モジュール全体をテスト可能とする # これを書かなくてもpython -m unittest -v コマンドでフォルダ内のテストコードを探索的に実行してくれる if __name__ == "__main__": unittest.main() |
大体のユニットテストフレームワーク
unittestモジュールは機能として最低限のものしか提供されていないため、pipを使ってサードバーティーのテストフレームワークを使うのも手。
本書ではpy.testというツールを紹介している。
1 2 3 4 5 6 7 8 |
# インストール pip install pytest # テストの実行 # 以下のルールでテスト対象が自動で探索される # 1:testから始まるファイル名内のTestから始まるクラス # 2:testから始まるファイル名内のtestから始まる関数 py.test -v |
xUnit系で定番のテスト実行前〜後のセットアップ処理の仕組みや、パラメタライズストなど定番の機能が一通り揃っている。
下記ページがかなり詳しいのでここを参照すると良い。
https://www.m3tech.blog/entry/pytest-summary