0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【書評】Fluent Python ―Pythonicな思考とコーディング手法

0
Posted at

はじめに

Pythonは「習得が簡単な言語」と紹介されることが多い言語です。実際、他の言語の経験があれば数時間で動くコードは書けるようになります。しかし、そこには落とし穴があります。動くコードは書けても、それがPythonらしいコードとは限らないという問題です。

本書『Fluent Python』は、まさにこの落とし穴を正面から扱った一冊です。著者のLuciano Ramalho氏はまえがきで、他言語の経験者は「Pythonにも正規表現はあるはずだ」とは調べるが、「タプルのアンパック」や「ディスクリプタ」のような存在自体を知らない機能は検索すらしない、と指摘しています。そして、Python固有だからという理由でその機能を一生使わずに終わる、と。

本書はその状態から抜け出すための本です。今回は設計・アーキテクチャに関心のあるエンジニア視点で、本書の内容と読みどころをまとめます。


書籍情報

項目 内容
原題 Fluent Python
邦題 Fluent Python ―Pythonicな思考とコーディング手法
著者 Luciano Ramalho
訳者 豊沢聡、桑井博之、梶原玲子(監訳:豊沢聡)
出版社 オライリー・ジャパン
対象バージョン Python 3.4(CPython 3.4でテスト)
構成 6部21章+付録

なお、原書には第2版があり、その邦訳『Fluent Python 第2版』も2025年11月にオライリー・ジャパンから刊行されています。本記事で扱うのは初版です(版の違いについては後述します)。


本書の位置づけ

対象読者

本書のまえがきには、対象読者が明確に書かれています。

  • 対象:Python 3を習熟したい現役のPythonプログラマ
  • 対象外:Pythonを学び始めたばかりの人

「Pythonを学び始めたばかりだと、本書についていくのは難しいと思います」と、著者自身が断言しています。ここは正直な記述で、実際そのとおりです。文法の説明はほぼありません。

本書が扱わないこと

著者は「本書はすべてを網羅したPythonの入門書ではありません」と明言しています。焦点を当てているのは、Pythonに特有か、他の多くの言語には存在しない言語機能です。

そのため、Web開発、データ分析、機械学習といった応用分野の話は一切出てきません。徹頭徹尾、言語そのものと標準ライブラリの話です。この割り切りが本書を分厚くも一貫したものにしています。

実践的なアプローチ

本書の特徴として、Pythonの対話型コンソール(REPL)が全編で多用される点が挙げられます。しかも単なる例示ではなく、掲載されているコード例のほとんどがdoctestでチェックされています。

つまり、書かれているコンソールセッションは実行して同じ結果になることが保証されているわけです。読みながら手元で試す、という読み方が非常にやりやすい構成になっています。


全体構成

本書は6部21章で構成されています。各部の章は順番に読むことを想定していますが、部単位では比較的独立しています。

テーマ
第Ⅰ部 プロローグ 1章
第Ⅱ部 データ構造 2〜4章
第Ⅲ部 オブジェクトとしての関数 5〜7章
第Ⅳ部 慣用的なオブジェクト指向 8〜13章
第Ⅴ部 制御フロー 14〜18章
第Ⅵ部 メタプログラミング 19〜21章

以下、部ごとに要点を見ていきます。


第Ⅰ部 プロローグ:Pythonのデータモデル

わずか1章ですが、本書全体の土台になる部です。

Pythonを他のオブジェクト指向言語のあとに学ぶと、collection.len()ではなくlen(collection)と書くことに違和感を覚えます。著者はこれを「氷山の一角」と表現します。

特殊メソッドという「フレームワーク」

本書はPythonのデータモデルを、言語自体が提供するフレームワークとして説明します。フレームワークのもとでコードを書くとき、私たちはフレームワークから呼び出されるメソッドを実装します。Pythonのデータモデルもまったく同じ構造です。

obj[key]と書けば、インタプリタがobj.__getitem__(key)を呼びます。この対応関係が、次のような言語構成すべてに存在します。

  • イテレーション
  • コレクション
  • 属性アクセス
  • 演算子オーバーロード
  • 関数・メソッド呼び出し
  • オブジェクトの生成と削除
  • 文字列表現と書式
  • コンテキスト管理(withブロック)

「ダンダー」という呼び方

__getitem__のような特殊メソッドは、俗にマジックメソッドと呼ばれます。本書ではダンダーメソッド(dunder = double underscore)という呼称を採用しており、__getitem__は「ダンダー・ゲットアイテム」と読みます。

この章で示されるのは、__len____getitem__のたった2つを実装するだけで、自作クラスが以下をすべて獲得するという事実です。

import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])

class Deck:
    def __init__(self):
        self._cards = [...]  # カードのリスト

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

これだけで、len(deck)deck[0]、スライス、forによるイテレーション、in演算子、さらにはrandom.choice(deck)まで動きます。

標準の作法に乗ることで、標準ライブラリの資産がそのまま使える。この設計思想が本書全体を貫いています。


第Ⅱ部 データ構造

2章:シーケンス

Pythonは、シーケンスの統一的な扱い方をABC言語から継承しています。文字列、リスト、バイト列、配列、XML要素、DBからの応答が、イテレーション・スライス・ソート・連結という共通操作を共有します。

本書はシーケンスを2軸で分類します。

  • コンテナ/フラットlisttupledequeは異なる型を格納できる。strbytesarrayは同じ型の値を自身のメモリ空間に直接格納する
  • 可変/不変listbytearraydequeなど vs tuplestrbytes

この分類を頭に入れておくと、未知のシーケンス型に出会ったときの見当がつくようになります。

リスト内包表記(listcomp)とジェネレータ式(genexp)の使い分けも丁寧です。Python 3では内包表記が独自のローカルスコープを持つようになり、Python 2で悩まされた変数リークが解消された点にも触れています。

なお著者は「リスト内包表記が2行以上になるようなら、分割するかforループで書き直したほうがよい」とも書いています。機能の紹介と同時に節度も示すのが本書の良いところです。

3章:ディクショナリとセット

dictはユーザコードで使うだけの存在ではありません。モジュールの名前空間、クラスとインスタンスの属性、関数のキーワード引数といったPythonのインフラそのものがdictで実装されています。

本章はハッシュテーブルの仕組みまで踏み込みます。そのうえで、キーの型の制限や順序の不確定性といった注意点を導出していく構成です。

「ハッシュ可能」の定義も丁寧です。公式ドキュメントの「不変な組み込みオブジェクトはすべてハッシュ可能」という記述は正確ではない(ハッシュ不可能なオブジェクトを含むタプルはハッシュ不可能)という指摘まで入ります。

4章:テキストとバイト

個人的に最も実務で効いた章です。

Python 3は「人が書く文字」と「生のバイト列」を厳密に区別しました。本章はその境界を、コードポイント・エンコーディング・正規化まで含めて扱います。

s = 'café'
len(s)                # 4(Unicode文字として4文字)
b = s.encode('utf8')
len(b)                # 5('é'がUTF-8で2バイトになる)
b.decode('utf8')      # 'café'

エンコード/デコードの方向を覚えるコツとして、著者は次のように言います。バイト列は機械語のコアダンプ、strは「人間」のテキスト。だから機械語のコードを人間向けに戻すのが「デ・コード」だ、と。

正規化による安全な文字列比較、ケースフォールディング、ロケール依存のソート、PyUCAライブラリまで扱っており、多言語対応が必要なプロダクトでは繰り返し参照することになる章です。


第Ⅲ部 オブジェクトとしての関数

5章:第1級関数

Pythonの関数が第1級オブジェクトであること、つまり実行時に生成でき、変数に代入でき、引数として渡せ、戻り値として返せることの意味を扱います。

高階関数、mapfilterreduceとリスト内包表記の関係、無名関数の使いどころ、7種類の呼び出し可能オブジェクト、__call__によるユーザ定義の呼び出し可能オブジェクトなどが順に登場します。

6章:第1級関数を使ったデザインパターン

設計に関心がある人にとって、本書のハイライトの一つだと思います。

Peter Norvigは1996年の発表で、GoFの23パターンのうち16パターンは動的言語では「不用にできるか、よりシンプルにできる」と述べました。本章はこれをPythonで実証します。

具体的には、ECサイトの割引ルール(ロイヤリティ割引、一括購入割引、大量購入割引)を題材に、教科書どおりのStrategyパターン実装を提示したうえで、それを関数オブジェクトでリファクタリングします。結果としてクラス階層が消え、コード量が大幅に削減されます。Commandパターンについても同様の簡略化が示されます。

重要なのは、GoF本自身が導入部で「どのパターンが妥当かは実装言語で決まる」と認めている点を著者が引用していることです。パターンは言語非依存だが、パターンの必要性は言語依存という整理は、DDDやクリーンアーキテクチャを別言語で学んだ人がPythonに来たときに効きます。

7章:関数デコレータとクロージャ

デコレータ構文の評価タイミング、変数のローカル判定ルール、クロージャの実体、nonlocalが必要になる理由を、順を追って詰めていきます。

@decorate
def target():
    ...

# 上は下と等価
def target():
    ...
target = decorate(target)

この等価性から出発して、登録デコレータ、functools.wrapslru_cachesingledispatch、そしてパラメータ化されたデコレータまで到達します。デコレータを「なんとなく使っている」状態から抜けるには、この章を通しで読むのが早道です。


第Ⅳ部 慣用的なオブジェクト指向

本書の最大の部で、6章分あります。

8章:オブジェクト参照、可変性、リサイクル

「変数は箱ではなく、オブジェクトに貼られたラベルである」という比喩から始まります。

著者はMITでLynn Andrea Stein教授から、「箱としての変数」という比喩がオブジェクト指向言語の参照変数の理解を妨げると指摘された経験を紹介しています。そして、代入を語るときは「変数sがシーソーに割り当てられる」と言うべきで、「シーソーが変数sに割り当てられる」とは決して言わない、という言葉づかいの厳密さにも触れます。

同一性・値・エイリアス、浅いコピーと深いコピー、可変なデフォルト引数の罠、弱参照とガベージコレクション。地味ですが、現場のバグの温床そのものを扱う章です。

9章:Pythonicなオブジェクト

2次元ベクトル型を育てながら、__repr____str____bytes____format__、クラスメソッドによる別コンストラクタ、書式指定ミニ言語の拡張、ハッシュ可能化、__slots__によるメモリ節約を実装していきます。

repr()は開発者向け、str()はユーザ向けという役割分担の説明が明快です。

10章:シーケンスをばらして、ハッシュして、スライスする

9章の2次元ベクトルを多次元に拡張し、標準の不変フラットシーケンスとして振る舞うクラスを作ります。スライス対応、要素数が多いインスタンスの安全な表現、__getattr__による動的属性アクセスなど、実装がかなり本格的になります。

11章:インタフェース——プロトコルからABCへ

設計観点で最も示唆に富む章でした。

前半はダックタイピングに基づく非形式的なプロトコル、後半は抽象基底クラス(ABC)です。ABCがPythonに導入されたのは2.6、つまり言語誕生から15年後だという事実から章が始まります。

そして著者は明確に警告します。

抽象基底クラスを手当たり次第に自作するのはお勧めしません。抽象基底クラスのやりすぎはよくあることです。

ディスクリプタやメタクラスと同様、ABCはフレームワーク構築のためのツールであり、アプリケーションコードで濫用するものではない、という立場です。Javaから来た人が最初にやりがちな「まずインタフェースを切る」設計への、明確なカウンターになっています。

registerによるサブクラス化なしのインタフェース宣言、__subclasshook__による自動認識まで扱います。

12章:継承の功罪

組み込み型のサブクラス化の落とし穴と、多重継承・MROを扱います。

組み込み型(C実装)は、ユーザ定義クラスでオーバーライドされた特殊メソッドを呼ばないことがあります。

class DoppelDict(dict):
    def __setitem__(self, key, value):
        super().__setitem__(key, [value] * 2)

dd = DoppelDict(one=1)   # __init__ はオーバーライドを無視する
dd['two'] = 2            # [] 演算子は __setitem__ を呼ぶ
dd.update(three=3)       # update もオーバーライドを無視する

対策としてcollections.UserDictなどを使う指針が示されます。

多重継承の説明では「おもちゃな例」を避け、TkinterとDjangoという実在のプロジェクトのクラス階層を題材にしています。良い例と悪い例の両方を実コードから引くのは、なかなか勇気のいる構成です。

13章:演算子オーバーロードの適切な用法

タイトルに「適切な用法」とあるとおり、機能紹介ではなく節度の話が中心です。

Pythonは柔軟性・使いやすさ・安全性のバランスを取るため、以下の制約を課しています。

  • 組み込み型の演算子はオーバーロードできない
  • 新しい演算子は導入できない
  • isandornotはオーバーロードできない(ビット演算子&|~は可能)

そのうえで、異なる型のオペランドが来たときの処理順序、NotImplementedを返すべき場面、拡張比較演算子の特殊な扱い、累算代入演算子(+=)のデフォルト挙動を詰めていきます。


第Ⅴ部 制御フロー

14章:イテラブル、イテレータ、ジェネレータ

iter()の内部動作から始まり、Iteratorパターンの古典的実装をジェネレータ関数・ジェネレータ式へ置き換えていく流れです。

イテレータとジェネレータの違いも明確にされます。イテレータはコレクションから要素を取得するもの、ジェネレータは要素を「何もないところ」から生成できるもの。無限数列を扱えるのはこのためです。

itertoolsの汎用ジェネレータ関数の使い分け、yield from、そして大規模データセット向けの変換ユーティリティのケーススタディまで扱います。

15章:コンテキストマネージャとelseブロック

forwhiletryelseブロックという、知られていないわりに有用な機能から入ります。

  • for:ループが最後まで実行されたときのみelseを実行(breakで抜けたら実行されない)
  • while:条件が偽になって抜けたときのみ実行
  • try:例外が発生しなかったときのみ実行

著者は「elseというキーワード選択は良くない、thenのほうが意味が通る」と率直に書いています。

with文とコンテキストマネージャについては、Raymond Hettinger氏の「コンテキストマネージャはサブルーチンそのものと同じくらい重要になるかもしれない」という言葉を引いて、ファイルを閉じる用途にとどまらない適用範囲を示します。contextlib@contextmanagerデコレータの使いどころも実践的です。

16章:コルーチン

yieldの二つの意味——「生成する」と「譲る」——から説明が始まります。

ジェネレータのyieldnext()の呼び出し元に値を渡すのに対し、コルーチンのyielddatum = yieldのように式の右辺に現れ、.send(datum)で呼び出し元からデータを受け取ります。

PEP 342(Python 2.5)で.send().throw().close()が追加され、PEP 380(Python 3.3)でyield fromが入った、という進化の道筋をたどる構成なので、なぜこの形になったのかが理解しやすくなっています。

yield制御フローの装置として捉え直せるかどうかが、この章の理解の分かれ目です。

17章:futuresを使った並行処理

concurrent.futuresを扱います。国旗画像を20枚ダウンロードするスクリプトを、逐次版・スレッドプール版・asyncio版の3種類で実装して比較する、という非常に分かりやすい題材です。

Michele Simionato氏の「アプリケーションプログラマが出くわす事例の99%で必要なのは、独立したスレッドを生み出してキューに結果を集めるというシンプルなパターンくらい」という引用が、この章のスタンスを表しています。

18章:asyncioによる並行処理

Rob Pikeの定義——並行処理はたくさんのことに一度に「対処」すること、並列処理はたくさんのことを一度に「処理」すること——を採用し、両者を明確に区別します。

イベントループ、コルーチン、ノンブロッキングI/Oの組み合わせでどう並行処理が成立するかを、17章と同じ題材で説明します。

なお本章は初版執筆時点(Python 3.4)の記述であるため、@asyncio.coroutineyield fromベースです。現在のasyncawait構文とは表記が異なりますが、イベントループとコルーチンの関係という本質は変わりません


第Ⅵ部 メタプログラミング

19章:動的属性とプロパティ

プロパティが「統一アクセス原理」(Uniform Access Principle)を満たすものだ、という位置づけから始まります。実装がストレージ経由か演算によるものかに関わらず、同じ表記でアクセスできるべきだという原則です。

__getattr____setattr__による仮想属性、プロパティによる検証、プロパティファクトリと、実践的な技法が並びます。題材はOSCON 2014のJSONデータで、半構造化データを扱う現実的なケースです。

20章:属性ディスクリプタ

Django ORMやSQLAlchemyのフィールド型がディスクリプタである、という説明から入ります。property自体がディスクリプタプロトコルの完全実装であることも示されます。

19章のプロパティファクトリ(関数型のアプローチ)を、ディスクリプタクラス(オブジェクト指向のアプローチ)へリファクタリングする流れが秀逸です。同じ問題に対する2つの解法を並べて見せることで、それぞれの適性が浮き彫りになります。

21章:クラスメタプログラミング

クラスファクトリ、クラスデコレータ、メタクラスと進みます。インポートタイムとランタイムの区別も扱います。

そして章の冒頭で、著者は最も強い警告を出します。

フレームワークを書いているのでないのなら、お遊びや練習を除いて、メタクラスは書くべきではありません。

実際、Python 2.6でクラスデコレータが導入されて以降、実用コードでメタクラスを正当化するのは非常に難しくなった、とも書かれています。最も強力な道具の章で、最も強く使用を戒めるという構成に、この本の誠実さが表れていると思います。


設計・アーキテクチャ観点での学び

読み終えて、設計に効く観点として残ったのは次の5点でした。

1. 言語のプロトコルに乗ることが最大の再利用

__len____getitem__を実装するだけで、標準ライブラリと言語構文の資産がまるごと使えるようになります。独自インタフェースを定義する前に、既存のプロトコルに適合できないかを考える。これは言語を問わず通用する発想です。

2. パターンの必要性は言語に依存する

Strategy、Command、Template Method、Visitor。第1級関数のある言語では、これらはクラス階層を必要としません。パターンを覚えることと、パターンが解こうとしている問題を理解することは別だ、と痛感させられます。

3. 抽象化ツールには使用制限がある

ABC、ディスクリプタ、メタクラス。本書はこれらをフレームワーク構築のためのツールと繰り返し位置づけ、アプリケーションコードでの濫用を戒めます。抽象化のコストを常に意識させる態度は、そのままアーキテクチャ設計の指針になります。

4. 継承より合成、そして継承の危険性の具体化

「継承より合成」は言い古された標語ですが、本書は組み込み型サブクラス化の実挙動という具体的な失敗例でそれを裏づけます。標語ではなく現象として理解できるのが強みです。

5. 統一アクセス原理の実装としてのプロパティ

パブリック属性で始めて、必要になったらプロパティに変える。呼び出し側のコードを壊さずにこれができるからこそ、Pythonでは最初からgetter/setterを書く必要がありません。これは設計の遅延判断を可能にする言語機能と言えます。


注意点:版について

本記事で扱った初版はPython 3.4を対象としています。そのため、以下は現在の記述と異なります。

  • asyncio周辺が@asyncio.coroutineyield fromベース(現在はasyncawait
  • dictの順序保証、f-string、データクラス、型ヒント、構造的パターンマッチング(match文)などが未収録
  • Python 2からの移行を意識した記述が随所にある

言語のコアな考え方(データモデル、プロトコル、ダックタイピング、ディスクリプタ)は現在も変わっていないため初版でも十分読む価値がありますが、これから買うなら第2版を選ぶのが順当だと思います。第2版はデータクラスビルダーや型ヒントを含む構成に再編されており、各章の冒頭に「第2版での変更点」が置かれています。


こんな人におすすめ

向いている人

  • 業務でPythonを書いていて、そろそろ「動くコード」の先に進みたい人
  • 他言語からPythonに来て、書き方に自信が持てない人
  • ライブラリやフレームワークを設計する立場の人
  • 言語の設計思想そのものに興味がある人

向いていない人

  • Pythonの文法をこれから学ぶ人
  • 特定のフレームワークの使い方を知りたい人
  • すぐ使えるレシピ集を求めている人

まとめ

『Fluent Python』は、Pythonの機能カタログではありません。Pythonがなぜその形をしているのかを説明する本です。

そして本書がユニークなのは、強力な機能を紹介するたびに「ただし濫用するな」と釘を刺してくる点です。リスト内包表記は2行を超えたら分割せよ、ABCを手当たり次第に作るな、フレームワークを書いていないならメタクラスを書くな。道具の紹介と同時に、その道具の限界と適用範囲を示すという姿勢が全編で一貫しています。

これは言語の解説書としてだけでなく、設計の教科書としても価値があるポイントだと感じました。Pythonを書く人はもちろん、他の言語で設計をしている人が読んでも、得るものは少なくないはずです。

厚い本ですが、部単位で独立しているので、興味のある部から読み始めるのが良いと思います。個人的には、設計に関心があるなら**6章(第1級関数を使ったデザインパターン)と11章(インタフェース)**から入るのをおすすめします。


参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?