パーフェクトPython

Python3 に特化した専門書という位置付けですが、(Python3 に関した) 言語仕様やその変更、ライブラリの詳細の違いなどを除けば、Python2 でも活用できる知識が大半です。まだ Python2 しか使っていないという方でも十分に役立つ内容だと思います。

ただ、本書を読み進める上で1つだけ忘れてはいけないことがあります。Python 全般について丁寧に解説されていますが

著者名が Python サポーターズとなっていますが、ついったーなんかでは Python モヒカンズなんて言われています。

パーフェクト Python の執筆に参加しました — プログラマのネタ帳 二冊目

と何だかこわい人たちが書いた本だということです。まずはそのことを念頭におくことで本書を楽しんで読み進められる心の準備が整った、言い換えると、覚悟はできたと言えます。そうすれば、途中でこわくなってきても勇気をもって立ち向かえますからね。

気になったところや興味深かったところを付箋付けしていったらこんなことになってしまいました。こわいことがたくさんです。

目次は以下の通りです。

1章 Pythonの概要

  • Part2 言語仕様

2章 Pythonの基本
3章 型とリテラル
4章 制御構文
5章 関数
6章 クラス
7章 モジュールとパッケージ
8章 拡張モジュールと組み込み
9章 標準ライブラリ

  • Part3 実践的な開発

10章 コマンドラインユーティリティ
11章 チャットアプリケーション
12章 アプリケーション/ライブラリの配布
13章 テスト
14章 Webプログラミング

  • Part4 適用範囲

15章 学術/分析系ライブラリ
16章 マルチメディア
17章 ネットワーク
18章 データストア
19章 運用/監視

Appendix
A 環境構築
B 標準ライブラリ

章ごとにこわくなったところをみていきます。

追記:
内輪的なものが気になるという意見も頂きました。
悪い意図はなかったので気になる方は以下のように置き換えて読んでください。

こわい = (個人的に) 興味がある、気になる、分からなかった、おもしろかった、不思議に思った

1章 Pythonの概要

Python の禅

Python の禅を全て紹介していた書籍は、私が読んだ中ではなかったように思います。こういった思想的なものは、最初のうちは言葉通りでしか理解できませんが、Python でプログラムを書くのに慣れていったり、開発コミュニティでやり取りしていくうちに、議論の根幹の部分の、共感できる要素になっていったりする気もします。

最初に思想的なところから啓蒙してくる辺り、Python サポーターズに洗脳されるんじゃないかと警戒したくなります。まだ初めの方なのでこわがらずに Python サポーターズの価値観に共感して読み進めましょう。

  • Beautiful is better than ugly.

最後の方に PerlRuby正規表現Python との違いをさらっと紹介しています。Python は言語の機能として正規表現をサポートせず、re という標準モジュールにより提供していることの背景を期待して読みましたが、、、書かれていませんでした。

なまじ説明がないのは「分かれよ」というメッセージです。Python正規表現リテラルをサポートしないのは、記号の多用を避けることを意図したものだったような気がしますが、情報ソースがどこだったのか見つけられませんでした。私の解釈があっているのか、ちょっとこわいです。

  • Flat is better than nested.

Java のパッケージの名前空間は長くて冗長だというものに対する否定やユーモアからの禅です。厳密に言うと、Java のパッケージは、ファイルシステムの階層構造と一致させる必要はありませんが、慣習的に (分かりやすさのために) そうなっているようです。Python のパッケージは __init__.py を置く決まりになっているのでファイルシステムと一致させる必要があります。データ構造やプログラミングの階層構造のことだと思わせておいて Java を dis ってるのがこわいです。

column: 名前の由来

本書では、spam や egg という変数名を使ったサンプルコードがたくさん紹介されていて、モンティ・パイソンから引用したものだとコラムにあります。

プログラムの挙動を説明するときなど、短いスニペットに使うときもありますが、意味のない名前を付けるのはなるべく避ける方が良いです。というのは、プログラミングの重要な要素の1つに「名前を付ける」ということがあります。ここで言うのは以下のような意味です。

  • 適切な名前を付ける
  • 分かりやすい名前を付ける
  • 簡潔な名前を付ける

この名前付けというのは、簡単なようでプログラムの規模が大きくになるにつれ難しくなります。変数名に限らず、小さい関数に分割しても関数名で悩み、クラス名で悩み、モジュール名で悩みます。その処理そのもの、責務、意図といった多くの概念を簡潔に表そうと考えるほど、名前を付けるのは難しいものです。

spam や egg というのは、ただの名前の過ぎないので、プログラマーにとって重要な「名前を付ける」という素養を軽視するのは、あまり良くない習慣だと言えます。こんなことを書いて大丈夫か、自分がこわいです。

IPython のオートコンプリート機能

IPython の機能の1つとしてコード補完が紹介されています。

私も普段は IPython を使っていますが、サーバーでのデバッグなど、IPython がインストールされていない環境でコード補完を行いたいときもあるでしょう。readline モジュールがインストールされている必要がある点は同じですが、IPython をインストールしなくても Python の標準ライブラリでコード補完できます。

>>> import readline, rlcompleter
>>> readline.parse_and_bind('tab:complete')

覚えておくと、ちょっとしたときに便利です。

たぶん Python サポーターズは、コード補完なんて軟弱な機能を使っていません。こわいです。

3章 型とリテラル

ucs2 と ucs4

PyCon JP 2012 で Dan Kogai 氏が指摘されていたのが印象に残っています。

Python 3.2 まではビルドオプションで ucs2 と ucs4 のどちらかを指定していました。この違いにより、16bit で表せるコードポイントを超えた部分に定義された文字列長が違ってしまうという問題がありました。Python 3.3 (PEP 393) からその問題が解消され、正しい文字列長が返されるようです。Python後方互換性を重視するため、マイナーバージョンアップにより以前書いたコードが動かなくなることはありませんが、あるバージョンから内部の仕組みが変わることはあります。

多くの人はディストリビューションが提供するバイナリを使うため、どちらのビルドオプションか意識せずに使っているでしょう。いまが過渡期であることの、問題の1つですね。

Python サポーターズは自分でビルドしているそうです。こわいです。

sys.intern

私自身、こういった最適化を行いたい状況に遭遇したことはありませんが、http://d.hatena.ne.jp/atsuoishimoto/20110418/1303094478 で知り、そんな仕組みがあるんだと印象に残っている機能です。

>>> import sys
>>> 'spam'.lower() is 'spam'.lower()
False
>>> sys.intern('spam'.lower()) is sys.intern('spam'.lower())
True

これはこわくありません。

タプルの構文

要素が1つの場合には tuple である事が解らなくならないように、(1,) のように末尾にカンマを記述します。

これは少し誤解を招く表現です。というのは、分からなくならないようにこのような記述をするのではなく、tuple の構文はカンマで区切るものであり、カッコは人間が分かりやすいように付けているものだからです。

>>> type((1))
<class 'int'>
>>> type((1,))
<class 'tuple'>
>>> 1,
(1,)

要素1のリストはカンマが必要でないので混同しがちなところかもしれません。

>>> [1]
[1]
>>> type([1])
<class 'list'>

カンマを忘れて tuple のつもりが int だったりしたらこわいです。

追記: 補足もらった、ありがとう

4章 制御構文

column: 内包表記のメリット

シンプルなループは積極的に内包表記を使った方がパフォーマンス面でメリットがあります。

>>> r = [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(r)
<class 'list'>

私は map の方がコーディング的には好みですが、Python3 からはリストが返されるのではなく map オブジェクトが返されるようになりました。

>>> r = map(lambda x: x, range(10))
>>> type(r)
<class 'map'>
>>> r[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'map' object is not subscriptable

map オブジェクトは、ジェネレーターとしては判別されないみたいですね。

>>> import types
>>> isinstance(r, types.GeneratorType)
False

結果をリストで扱いたいときは組み込み関数の list で変換できますが、そういった用途なら内包表記を使う方が簡潔で良いですね。

>>> r = list(map(lambda x: x, range(10)))
>>> r[1]

従来からのリスト内包表記に加えて、セット内包表記、辞書内包表記も使えるようになりました。

>>> {x for x in [1, 2, 3, 2, 1, 5]}
{1, 2, 3, 5}
>>> {key: value for key, value in zip(['a', 'b'], [1, 2])}
{'b': 2, 'a': 1}

(慣れの問題で) ちょっと違和感も感じますが、コーディング的におもしろいです。こわくはありません。

例外オブジェクトの特殊メソッド

前後に __ (私はアンダーアンダーと呼んでます) で囲まれたメソッドを、Python では特殊メソッドと呼びます。ある人は、特殊メソッドは「Python の黒魔術だ」と表現していましたが、どんな特殊メソッドがあるのか知っておくというのは Python のプログラミングで重要なことです。

例外オブジェクトで3つの特殊メソッドが紹介されていました。

  • __traceback__: 例外発生時のトレースバックが格納される
  • __context__: 例外発生中に例外が発生したとき、元の例外オブジェクトが格納される
  • __cause__: "raise Error from err" としたとき、元の例外オブジェクトが格納される

この手のものは、時間とともにどんどん増えていきそうな雰囲気を感じます。便利なのは便利なのでしょうが、バージョンにより使える特殊メソッドがあるというのは意識しておく必要がありそうです。

Python サポーターズは、特殊メソッド を全て暗記しているそうです。こわいです。

5章 関数

サブジェネレーターへの委譲

Python 3.3 の目玉機能の1つです。

def generator():
    yield from sub_generator()

この処理をちゃんと実装しようとすると、かなり煩雑になるというのが PEP 380 Formal Semantics にあります。PEP 380 は粗訳したまま放ったらかしです、こわいというかごめんなさい。

6章 クラス

super とメソッドの検索順序 (mro)

Python3 から super を呼び出す引数を省略できるようになったのが簡潔で良いですね。

super と mro は、Python で委譲を実装する仕組みの1つです。super のメリットが分かる簡単なサンプルを紹介します (Pythonのsuper()ができること から)

  • 継承元の基底クラスを変更するときにコードを書き換える必要がない
  • サブクラス側でスーパークラスのデフォルトの委譲先を変更できる
>>> class MyDict(dict): pass
... 
>>> MyDict.__mro__
(<class '__main__.MyDict'>, <class 'dict'>, <class 'object'>)
>>> class YourDict(dict): pass
... 
>>> class OurDict(MyDict, YourDict): pass
... 
>>> OurDict.__mro__
(<class '__main__.OurDict'>, <class '__main__.MyDict'>, <class '__main__.YourDict'>, <class 'dict'>, <class 'object'>)

MyDict の mro は、MyDict->dict->object の順番だったのが、YourDict を定義して、OurDict で MyDictとYourDict を多重継承した場合、MyDict の次の mro が YourDict に変更されていることが分かります。例えば、MyDict の __init__ でスーパークラスの __init__ の呼び出しを super で行った場合、MyDict を修正することなく dict.__init__ から YourDict.__init__ へ変更できるというわけです。

>>> class MyDict(dict):
...   def __init__(self):
...     super().__init__()  # mro により、dict か YourDict かを解決してくれる

本書のサンプルにあった、多重継承の「もう少し複雑なケース」はよく分からなかったので読み飛ばしました。初心者に易しくないのがこわいです。

7章 モジュールとパッケージ

相対インポートと名前空間パッケージ

サードパーティのライブラリをインポートしたり、拡張プラグインを開発するときなど、ちょくちょくはまった経験があります。相対インポートと名前空間パッケージについて簡潔に説明されていて分かりやすいです。PEP 420 で Python3.3 からは名前空間パッケージが言語機能として提供されるようになったようです。

従来の pkg_resources によるおまじないは不要になるのですね。

__import__('pkg_resources').declare_namespace(__name__)

Python のパッケージングの仕組みは、一体どのライブラリの機能なのかよく分からなくなるのがこわいです。

9章 標準ライブラリ

組み込み型関数の memoryview

バッファプロトコルをサポートしたデータを扱い、コピーを作る事なく操作できるクラスです。

こんな組み込み型関数があったんだと初めて知りました。

>>> m = memoryview(b'abc')
>>> type(m)
<class 'memoryview'>

Python2.7 でも (バックポートされて?) 使えるみたいですね。

IO 周りのどんな処理や用途に使うと良さそうなのか、実例を見てみたいです。私はこわくて声をかけられませんが、誰か Python サポーターズに聞いてみてほしいです。

map の特殊な使い方

Python2 では map の第1引数に None を渡すと、足りない要素数を None で補う zip のような挙動をしていました (ライブラリリファレンスの map() の説明) 。

>>> map(None, [1,2,3], [4,5], [7,8,9])
[(1, 4, 7), (2, 5, 8), (3, None, 9)]
>>> zip([1,2,3], [4,5], [7,8,9])
[(1, 4, 7), (2, 5, 8)]

Python3 からはこのように扱ってくれないようです。

>>> list(map(None, [1,2,3], [4,5], [7,8,9]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
>>> list(zip([1,2,3], [4,5], [7,8,9]))
[(1, 4, 7), (2, 5, 8)]

変わりに itertools.zip_longest を使うようです。

>>> from itertools import zip_longest
>>> list(zip_longest([1,2,3], [4,5], [7,8,9]))
[(1, 4, 7), (2, 5, 8), (3, None, 9)]

2to3 では変換できなかったので移行のちょっとした tips ですね。

$ 2to3 map-none.py 
--- map-none.py (original)
+++ map-none.py (refactored)
@@ -1 +1 @@
-map(None, [1,2,3], [4,5], [7,8,9])
+list(map(None, [1,2,3], [4,5], [7,8,9]))

Python サポーターズは 2to3 を使わずに自分でコードを書き直すようです。こわいです。

pdb コマンド

インタラクティブデバッガも標準で付属していて必要十分なのが嬉しいですね。ツールが揃っていると、デバッグも楽しいものです。

  • pretty print

複雑なオブジェクトを調べるには from pprint import pprint してたのですが、pp でできることを知りました (> <)

(Pdb) list
  1     d = {'a': 1, 'b': 2, 'l': range(10), 't': tuple(range(10)), 'd': dict(x=9)}
  2  -> import pdb; pdb.set_trace()
[EOF]
(Pdb) p d
{'t': (0, 1, 2, 3, 4, 5, 6, 7, 8, 9), 'l': range(0, 10), 'a': 1, 'd': {'x': 9}, 'b': 2}
(Pdb) pp d
{'a': 1,
 'b': 2,
 'd': {'x': 9},
 'l': range(0, 10),
 't': (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)}
  • 条件付きブレイク

なぜか私の中では pdb で 条件付きブレイクはできないと思い込んでいました。これも Python2 の頃からできたんですね (- -#)

> /Users/t2y/work/book/review/perfect-python/pdb/pdb_sample2.py(3)<module>()
-> def f(x):
(Pdb) longlist
  1     import pdb; pdb.set_trace()
  2     
  3  -> def f(x):
  4         # do something
  5         return x
  6     
  7     for i in range(5):
  8         print(f(i))
(Pdb) break 5, x==3
Breakpoint 1 at /Users/t2y/work/book/review/perfect-python/pdb/pdb_sample2.py:5
(Pdb) c
0
1
2
> /Users/t2y/work/book/review/perfect-python/pdb/pdb_sample2.py(5)f()
-> return x
(Pdb) x
3
(Pdb) c
3
4
$ 

Python サポーターズの書くプログラムにはバグがないのでデバッガの使う機会はないんだと推測します。こわいです。

column: IDE

PythonIDE でメジャーなものを紹介していますが、あれ!?と思ったのが漏れていたので紹介します。

きっと Python サポーターズは IDE を使っていません。こわいです。

10章 コマンドラインユーティリティ

10-3-5 データを表示する

集計したデータを表示するところで、さらっと operator モジュールが使われていました。

def print_results(results):                                                         
    for key, value in sorted(results.items(), operator.itemgetter(1)):              
        print(key, value)    

operator モジュールの用途の1つとして、要素のゲッターとして使うとコードが簡潔に記述できるので、私は好んでよく使います。

>>> import operator
>>> d = {"x": 1, "y": 2}
>>> operator.itemgetter("x", "y")(d)
(1, 2)

>>> f = open('test.txt', mode='w', encoding='utf-8')
>>> operator.attrgetter('mode', 'encoding')(f)
('w', 'utf-8')

同処理をする Python の関数や lambda を使うよりパフォーマンス面でもメリットがあると、以前はドキュメントに記述されていた気がします。operator, functools, itertools, contextlib, collections といった標準ライブラリを使うようになってきたら段々こわくなる気がします。

12章 アプリケーション/ライブラリの配布

distribute の注意点

distribute には、プロジェクト内のパッケージを自動で発見する find_packages という関数があり、よく利用されています。しかし、Python 3.3 以降の名前空間パッケージが __init__.py を含まなくなったため、注意が必要です。

これもパッケージ配布の機能が標準で提供されることによる過渡期の課題ですね。覚えておくと、移行の tips になりそうです。移行というキーワードで話題を出したのに「昔からそうだよ」と返されるとこわいです。

13章 テスト

doctest の便利な記法

空行を意図する が紹介されています。

def safe_str(s):
    """
    >>> print(safe_str(None))
    <BLANKLINE>
    >>> print(safe_str("test"))
    test
    """
    if s is None:
        return ""
    else:
        return s

その他にも便利な機能がサポートされています。doctest は、コーディングしながら一緒にテストも書ける楽しさがありますね。

  • "..." でマッチングを省略する ELLIPSIS オプション
def myrange(x):                                                                     
    """                                                                             
    >>> list(myrange(10))  # doctest: +ELLIPSIS                                     
    [0, 1, ... 8, 9]                                                                
    """                                                                             
    return range(x)   
  • 空白文字類を無視する NORMALIZE_WHITESPACE オプション
def get_double_range(x):                                                         
    """                                                                          
    >>> get_double_range(10)  # doctest: +NORMALIZE_WHITESPACE                   
    ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9],                                             
     [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])                                             
    """                                                                          
    r = list(range(x))                                                           
    return r, r       

doctest で ELLIPSIS や NORMALIZE_WHITESPACE オプションを使うと、PEP8 の 80 文字制限も遵守できそうでこわいです。

後半の章

後半は主にアプリ開発やツール・ライブラリの紹介です。

  • 14章 Webプログラミング
  • 15章 学術/分析系ライブラリ
  • 16章 マルチメディア
  • 17章 ネットワーク
  • 18章 データストア
  • 19章 運用/監視

本当はいろいろ使ったり、開発したりする上でのコメントも入れたかったのですが、余裕がなくてそこまで至りませんでした。こうやって体系的にみると、いろんな分野で Python が使えるのが分かります。取りあえずで Python を始めてみても、後からあるドメインの処理のために別言語やツールを使わないといけない、といったケースは少ないかもしれません。

2012-04 に Hacker News で好きな言語として Python が選ばれていました。

書くことないから他の記事を引用しているというのが Python サポーターズにばれてないか、こわいです。

正誤表

最後に私が見つけた誤植等をまとめておきました。誰でも編集できるので皆さんも利用して共有してください。Python サポーターズへもこのサイトを連絡済みなので次刷で修正されると思います。

くれぐれも誤植を見つけても悪いのはネコなので文句を言ってはいけません。どんなこわいことがあるか分かりませんからね。

誤植があったらネコのせいです

emerge cat / bengal: 本を書いたらネコがクンスカした

まとめ

無理に Python3 を使う必要はなく、頑に Python2 を使う必要もないのがいまの状況な気がします。新しく開発するものでサードパーティのライブラリがあるなら Python3 で開発を始める時期に差し掛かっていると言えます。

2008年の12月にリリースしたのが Python 3.0 です。
(中略)
リリースから大凡4年がたち、2012年9月にはバージョン 3.3 になっています。

実は前バージョンの 2.0 がリリースされた後、実際に誰もが当然 2 系を使うようになるまで 5 年程度かかっています。各種サードパーティのライブラリが出そろうだけでも多くの時間を要します。Python の原作者である Guido van Rossam 氏は、今回の 3 系に関しても5年かかるだろう、と予測しています。

「2013年 Pythonの本格的な浸透」といった記事も見ましたが、着実に Python3 への移行は進んでいます。Ubuntu の次の LTS では Python3 しか提供しないという噂も聞いたりします。

本書を読んで Python3 プログラミングを始める人が増えて、日本でも Python コミュニティやその開発が活発になると嬉しいです。

Python サポーターズは、自分はこわくないですとみんな言いますが、本当はこわいと思います。