Python で Unicode 正規化 NFC/NFD の文字列を扱う

先日、ビジネスパーソン向けの Python 本を執筆したことを書きました。

t2y.hatenablog.jp

本稿では本書のことを「できるPy」と呼びます。

Amazon でいくつかカスタマーレビューもいただいて次のコメントをみつけました。

python3.7 対応ということで、pathlib を使ってる点が(古いpython は切り捨てる!的なところは)潔いと言えば潔いし、日本語のファイル名にも気を配っている記述はオライリーに期待するのは酷なところもある。でもこの本でもNFD問題は全くの記述無し。だめだろ、それじゃ。

Amazon CAPTCHA

まさに仰る通りです。執筆時にそのことに気づかずご指摘いただいてありがとうございます。

ここでご指摘されている NFD 問題というのは、ファイル名のみに限った問題ではなく、Unicode文字集合を扱ってエンコード/デコードするときに発生する問題です。ASCII 文字しか扱わない開発者が書いたコンテンツとの差別化を図るという意味でも付加価値になり得るコンテンツだと私も思います。

本稿では「できるPy」に掲載できなかった NFC/NFD 問題について説明しようと思います。私の一存では決められませんが、本書を改訂できるタイミングがあれば、出版社と相談して本稿の内容も追加してもらうように働きかけようと考えています。

本稿で紹介するサンプルデータならびにサンプルコードは次の URL からダウンロードできます。

NFC/NFD 問題とは

Unicode は世界中のすべての言語で使われる文字を扱う文字集合です。Python は内部的に Unicode で文字列を扱います。例えば、UTF-8エンコードされたファイルの内容を読み込むコードが次になります。

import sys

filename = sys.argv[1]
with open(filename, encoding='utf-8') as f:
    for line in f:
        print(line.strip())

Unicode では1つの文字を表すのに複数の文字を組み合わせることができます。そして、同じ文字が別々の表現だと検索や置換などに不都合があるのでどれか1つの表現に統一することを 正規化 と呼びます。その正規化形式の名前の頭文字をとって NFC、NFD と呼びます。

  • NFC: Normalization Form Canonical Composition (合成済みの文字)
  • NFD: Normalization Form Canonical Decomposition (複数文字を結合した文字列)

言葉だけではわかりにくいので実際にその違いをサンプルデータとともに説明します。いま NFC/NFD で正規化されたテキストファイルを前述したプログラムを使って中身を表示してみます。

実行結果。

$ python3 read_file_and_print.py NFC_sample1.txt
プログラミング

$ python3 read_file_and_print.py NFD_sample1.txt
プログラミング

どちらも「プログラミング」と表示されました。OS 環境や OS バージョンによって正しく表示されない可能性もあります。私の環境 (macOS 10.14.4) では見た目上は全く同じに表示されます。

このとき変数 line には UTF-8 でデコードされて Unicode の文字列がセットされます。次に NFC/NFD 問題をイメージしやすいように Unicode の文字列を文字単位で扱うサンプルを紹介します。

import sys
import unicodedata

def show_unicode_name(line):
    for char in line.strip():
        name = unicodedata.name(char)
        space = ' '
        if unicodedata.combining(char) != 0:
            space += ' '
        print(f'{char}{space}: {name}')

filename = sys.argv[1]
with open(filename, encoding='utf-8') as f:
    for line in f:
        text = [char for char in line.strip()]
        print(f'文字単位: {text}, 長さ: {len(text)}')
        show_unicode_name(line)

実行結果。

NFC で正規化されたテキストを文字単位で分割して Unicode データベースに登録されている名前を表示する。

$ python3 read_file_and_show_unicode_name.py NFC_sample1.txt
文字単位: ['プ', 'ロ', 'グ', 'ラ', 'ミ', 'ン', 'グ'], 長さ: 7
プ : KATAKANA LETTER PU
ロ : KATAKANA LETTER RO
グ : KATAKANA LETTER GU
ラ : KATAKANA LETTER RA
ミ : KATAKANA LETTER MI
ン : KATAKANA LETTER N
グ : KATAKANA LETTER GU

NFD で正規化されたテキストを文字単位で分割して Unicode データベースに登録されている名前を表示する。

$ python3 read_file_and_show_unicode_name.py NFD_sample1.txt
文字単位: ['フ', '゚', 'ロ', 'ク', '゙', 'ラ', 'ミ', 'ン', 'ク', '゙'], 長さ: 10
フ : KATAKANA LETTER HU
 ゚  : COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
ロ : KATAKANA LETTER RO
ク : KATAKANA LETTER KU
 ゙  : COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
ラ : KATAKANA LETTER RA
ミ : KATAKANA LETTER MI
ン : KATAKANA LETTER N
ク : KATAKANA LETTER KU
 ゙  : COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK

NFD で正規化された文字列では濁音や半濁音が別の文字として扱われていて、Unicode の文字として数えたときの文字数も異なっていることがわかります。例えば「プ (U+30D7)」と、「フ (U+30D5)」と合成用半濁点 (U+309A) の組み合わせで表す「プ」の文字は Unicode 上では別の文字なのでこの違いにより、検索や置換などで一致しないということが発生します。

このような Unicode における正規化の違いによって起こる問題のことを NFC/NFD 問題と呼ばれたりします。

NFC/NFD 問題が発生する状況

OS によって Unicode の正規化形式が異なります。Windows/Linux では NFC を採用しており、昔の macOS (Mac OS X) では NFD を採用していました。同じプラットフォーム上で日本語ファイル名や日本語のテキストを扱っている分には、この問題に遭遇することは少ないかもしれません。

また macOS では NFD を採用しているという記事をみかけますが、最近の macOSiOS では NFC を使うように変わっているそうです。ファイルシステムが HFS+ から Apple File System (APFS) に置き換えられたときに変わったのかもしれません。私はその背景に明るくないため、詳しい方がいましたら教えてください。

私の環境は macOS 10.14.4 ですが、APFS を使っているせいか、NFCUnicode 正規化が行われます。

$ mount
/dev/disk1s1 on / (apfs, local, journaled)

この NFC/NFD 問題は昔の macOS (Mac OS X) で作られた日本語ファイル名や、Unicode文字集合に用いる符号化方式でエンコードされたコンテンツを Windows/Linux 環境で扱うとき、もしくはその逆のときに発生します。一方で NFD を採用していたときの macOS (Mac OS X) であっても、例えば Web ブラウザからコピペしたコンテンツは NFC で扱われたりして、同じプラットフォーム上でも NFD と NFC が混在してしまうといったことも発生するそうです。そして、見た目上は全く同じ文字にみえるため、この問題に気付くのが難しいというのが NFC/NFD 問題の厄介なところです。

Python で NFD を NFC に正規化する

さて本題です。ここまでで NFC/NFD 問題の概要を説明しました。

Python では標準ライブラリで Unicode データベースを扱うライブラリ unicodedata が提供されています。このライブラリを使うことで Unicode 文字の情報を取得したり、読み込んだ文字列に対して NFC/NFD といった正規化形式の変換をやり直すこともできます。

docs.python.org

unicodedata.normalize() の引数に NFC や NFD といった正規化形式と Unicode 文字列を渡すと変換できます。

unicodedata.normalize('NFC', unistr)

また結合文字かどうかは unicodedata.combining() に文字を渡してゼロ以外が返ってくるかどうかで判断できます。

unicodedata.combining(chr)

次のサンプルコードでは、濁音、半濁音などの結合文字があるかどうかを調べて、結合文字がある場合は NFC に変換します。

import sys
import unicodedata

def is_nfd(line):
    for char in line.strip():
        if unicodedata.combining(char) != 0:
            return True
    return False

def show_unicode_name(line):
    for char in line.strip():
        name = unicodedata.name(char)
        space = ' '
        if unicodedata.combining(char) != 0:
            space += ' '
        print(f'{char}{space}: {name}')

filename = sys.argv[1]
with open(filename, encoding='utf-8') as f:
    for line in f:
        text = [char for char in line.strip()]
        print(f'文字単位: {text}, 長さ: {len(text)}')
        show_unicode_name(line)

        if is_nfd(line):
            print('NFD から NFC への変換')
            converted = unicodedata.normalize('NFC', line)
            show_unicode_name(converted)

実行結果。

$ python3 read_file_and_normalize.py NFD_sample1.txt
文字単位: ['フ', '゚', 'ロ', 'ク', '゙', 'ラ', 'ミ', 'ン', 'ク', '゙'], 長さ: 10
フ : KATAKANA LETTER HU
 ゚  : COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
ロ : KATAKANA LETTER RO
ク : KATAKANA LETTER KU
 ゙  : COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
ラ : KATAKANA LETTER RA
ミ : KATAKANA LETTER MI
ン : KATAKANA LETTER N
ク : KATAKANA LETTER KU
 ゙  : COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
NFD から NFC への変換
プ : KATAKANA LETTER PU
ロ : KATAKANA LETTER RO
グ : KATAKANA LETTER GU
ラ : KATAKANA LETTER RA
ミ : KATAKANA LETTER MI
ン : KATAKANA LETTER N
グ : KATAKANA LETTER GU

まとめ

PythonUnicode の文字列が NFC/NFD のどちらの形式で正規化されているか、また別の正規化形式に変換する方法を紹介しました。

最近の macOSNFC を採用していることからいまが過渡期で NFC/NFD 問題は徐々に NFC に統一されていって将来的にはこういった問題が起こらなくなるのかもしれません。一方で NFD という表現形式も Unicode で定義されている仕組みであり、この仕組み自体がなくなるわけではありません。

本稿では濁音・半濁音のみを紹介しましたが、特殊な記号を複数の記号の組み合わせで表現することもできます。Unicode にはこのような表現形式があるというのを覚えておくといつか役に立つことがあるかもしれません。

リファレンス

ja.wikipedia.org

en.wikipedia.org

www.slideshare.net

gihyo.jp

qiita.com

できる 仕事がはかどるPython自動処理 全部入り。を執筆しました

インプレスから「できる」シリーズの Python 版として発売されました。著者の1人として執筆に関わったので紹介します。

book.impress.co.jp

名前が長いので本稿では本書のことを「できるPy」と呼びます。誰も聞いていませんが、ハッシュタグ#dekiru_py です。

経緯

2018年3月頃に知人からビジネスパーソンを対象に本書の監修として企画・構成をしてほしいといった依頼をいただきました。そのとき、書き手はやる気のある若い人たちがたくさんいるからと、私が目次を作って全体の構成をまとめたら誰かがコンテンツを書いてくれて、私は出来上がったコンテンツをレビューするのが主な役割になるのかなと安易に考えていました。

結果的には私が他の執筆者全員に声をかけて書いてもらうようにお願いして、私自身も著者の1人として少なくない割合を執筆しました。

詳細な目次

インプレスサイトでは目次が公開されていません。Amazon のサイトにも章レベルの目次しかありません。店頭で目次チェックするのも面倒だと思うので詳細な目次を書いておきます。目次のタイトルは編集者さんが対象とする読者層、ビジネスパーソンな方々がわかりやすいようにつけてくれました。

次のような「ここもポイント」という見出しでちょっとした小話や発展的なヒントをいくつか書いています。その目次も一緒に含めておきます。

f:id:t2y-1979:20190525120231p:plain:w480

Chapter 1 Pythonのプログラムを利用する前に … 011

Pythonの紹介

  • 001 Pythonがビジネスに役立つってホント? … 012
    • ここもポイント | インデントルールの背景 … 014

実行環境の構築

実行環境の注意点

  • 004 OSによる違いを把握する … 032

Chapter 2 コマンドラインインターフェース … 035

コマンドラインインターフェース

インタラクティブシェル

Chapter 3 サードパーティライブラリのインストール … 059

パッケージマネージャ

  • 011 パッケージマネージャを使ってライブラリをインストールする … 060
    • ここもポイント | その他の処理系 … 061
  • 012 condaを使ったパッケージのインストール … 062
  • 013 pipを使ったパッケージのインストール … 067

Chapter 4 Pythonのおさらい … 069

プログラミング用語

  • 014 覚えておきたいPython&プログラミングの基本用語 … 070

用語と文法

  • 015 Pythonプログラミングの基本 … 074

基本テクニック

Chapter 5 ファイルの操作と圧縮・展開 … 097

ファイル一覧の取得

  • 018 フォルダ内のファイルを一覧表示する … 098
  • 019 特定種類のファイルだけを表示する … 101

ファイルの圧縮/展開

  • 020 ZIPファイルを作成/展開する … 103
  • 021 ZIPファイルに含まれるファイル名を文字化けさせずに表示する … 107
  • 022 ZIPファイルに含まれるファイル名を文字化けさせずに展開する … 116

Chapter 6 画像の加工 … 119

Pillowによる画像加工

  • 023 Pillowを使った画像処理の基本 … 120
  • 024 画像のサイズ変更と切り抜きを行う … 123
  • 025 画像を回転する … 125
  • 026 画像をモノクロにする … 126

画像の連続処理

  • 027 画像のサイズをまとめて調整する … 127

piexif

Chapter 7 CSVファイルの処理 … 137

CSVファイルの読み込み

  • 029 CSVファイルを読み込む … 138
    • ここもポイント | ちょっとした集計にも役立つ関数 … 142
  • 030 読み込んだCSVファイルを1行ずつ処理する … 143
  • 031 ヘッダー行がないCSVファイルを読み込む … 145
  • 032 文字コードを指定してCSVファイルを読み込む … 146

CSVファイルの書き込み

  • 033 CSVファイルを書き込む … 148
  • 034 列の順番を指定してCSVファイルを書き込む … 151
  • 035 インデックス列を出力せずにCSVファイルを書き込む … 152
  • 036 項目(値)にクォートを付けてCSVファイルを書き込む … 153
  • 037 タブ区切りのデータとしてファイルを書き込む … 158
  • 038 読み込んだCSVファイルに列を追加して書き込む … 159
  • 039 JSONデータを読み込んでCSVファイルを書き込む … 160

CSVビューアーの作成

  • 040 横に長いデータを縦向きで表示する … 163
  • 041 複数の値を扱うときに便利な組み込み関数 … 171
  • 042 指定した列番号の情報だけを表示する … 173
    • ここもポイント | 大きなプログラムを作るときのコツ … 174

Chapter 8 テキストデータの処理 … 175

文字列操作

  • 043 文字列の基本的な操作 … 176

正規表現

  • 044 正規表現を使った文字列の扱い … 184
  • 045 正規表現にマッチするすべての文字列を取り出す … 189
  • 046 HTMLファイルからタグを取り除く … 191
  • 047 非構造化テキストをディクショナリにする … 193

テキスト抽出

  • 048 Microsoft Wordからテキスト抽出 … 196
  • 049 Microsoft PowerPointからテキスト抽出 … 202
  • 050 PDFからテキスト抽出 … 207
  • 051 Markdown形式のドキュメントをHTMLに変換する … 215

形態素解析

  • 052 テキストから重要語句を抜き出す … 219

Chapter 9 Microsoft Excelとの連携 … 225

Excelとopenpyxl

ワークブックの操作

  • 054 ワークブックを扱う … 228
  • 055 既存のExcelファイルを読み込む … 230

セルの操作

  • 056 セルを扱う … 234

グラフの作成

  • 057 CSVファイルを読み込んでグラフを作成する … 239

条件付き書式

  • 058 条件付き書式を扱う … 247
  • 059 値や数式を使った条件付き書式を設定する … 254
  • 060 設定済みの条件付き書式を調べる … 257

Chapter 10 Webスクレイピング … 261

Webスクレイピングの概要

Beautiful Soup

  • 062 Webページから要素を取り出す … 264
  • 063 Webページ内の画像を取り出す … 272

Selenium

  • 064 Webブラウザを自動で制御するには … 276
  • 065 WebページをSeleniumで操作する … 279
    • ここもポイント | SeleniumとBeautiful Soupの違いと使い分け … 283

feedparser

  • 066 RSSを利用してニュースを取得する … 284

Chapter 11 Web API … 287

Web APIの概要

  • 067 Web APIとは … 288
    • ここもポイント | Web APIは変化する … 289

Google Sheets API

  • 068 Google Sheets APIの利用準備をする … 292
    • ここもポイント | 鍵ファイルの管理 … 297
  • 069 Googleスプレッドシートを操作する … 299
    • ここもポイント | 鍵ファイルとprepare_credentials関数 … 301

Google Calendar API

複数のWeb APIの組み合わせ

退屈なことはPythonにやらせよう ――ノンプログラマーにもできる自動化処理プログラミング

www.oreilly.co.jp

オライリー・ジャパンさんから2017年6月に出版されています。

本書の依頼をうけたときに出版社の編集者さんから「退屈なことはPythonにやらせよう」がビジネスパーソン向けに人気があるというお話を伺いました。2017年6月の出版で半年しか販売期間がなかったにも関わらず、次の2018年の技術書ランキングではトップ10内に君臨していてその人気ぶりが伺えます。

gihyo.jp www.shoeisha.co.jp

私も技術書ランキングなどで取り上げられているのを見聞きしていたので本の名前は知っていました。しかし、実際に読んではいませんでした。そこで「できるPy」の依頼を受けたときに購入してどのような内容かを確認しました。

「退屈なことはPythonにやらせよう」は616ページあります。これは Python 入門と実践的なプログラミングの2つの題材が1冊になっているからです。これから初めてプログラミングを学ぶ人にとっては入門から始めてステップアップしながら1冊でたくさんのことを学べます。インタラクティブシェルを使って実際に1行ずつコードを入力・実行しながらその結果を確認して理解していくといったスタイルで書かれています。実際に手を動かしながら学べることから初学者向けとしてとてもよい本だと私は思います。

書店巡り

執筆はそれなりに苦労したにも関わらず、もしくは苦労した甲斐もあって実際に書籍としてリアルな本を手に取ったとき、なんとなく嬉しくなって舞い上がってしまいました。コアタイムが過ぎたら退勤して、近くの書店巡りをして実際に置かれているか調べに行きました。

f:id:t2y-1979:20190523160422j:plain:w1024
丸善ジュンク堂さんの棚

f:id:t2y-1979:20190523162648j:plain:w1024
紀伊国屋さんの棚

私が巡った書店ではどちらも本書の師匠である「退屈なことはPythonにやらせよう」の隣に並べられていました。お客さんにとってどちらが良いかを比較して選びやすい最適な棚配置だと私は思います。

「できるPy」と「退屈なことはPythonにやらせよう」のどちらを選べばよいですか?

と質問をしたくなるでしょう。

もし質問者がこれからプログラミングを学び始めようという方であれば、私は迷わず「退屈なことはPythonにやらせよう」をお勧めします。

「退屈なことはPythonにやらせよう」は素晴らしい内容ですし、扱っている題材も多くすべての章節を読まなくても読者の興味がある分野をみつけるときに「できるPy」では扱っていない題材をみつけることもできます。

じゃあ「できるPy」は発売時点でオワコン?

いえいえ。そんなことはありません。

プログラミングの勉強に限った話ではありませんが、勉強というのは結局のところ独学が基本です。もちろん勉強会へ行ってモチベーションをあげたり、セミナーに通って講師に指導を受けたり、友だちにわからないところを教えてもらったり、いろんな勉強のスタイルはあります。しかしながら、ある程度のスキルを身に着けるには独学でたくさんの時間をかけて学ぶ必要があります。

書籍というのは最も身近で安価に独学を助ける教材の1つです。たくさんの書籍がある理由の1つとして、人それぞれに趣味趣向があり、本人が読みやすい分かりやすいと思う書籍の内容、構成、分量、体裁は異なると私は考えています。本書は熱意のある編集者さんが体裁を整えて校正してくれたおかげで初学者にとってかなり親切な内容になっていると私は思います。いくら詳細に説明されていても読まない本より、基本的なことしか書いていなかったとしても読む本の方が独学の助けになります。

大事なことは自分にとって気に入った本を選ぶこと、独学を続けられそうな本を選ぶことだと私は思います。その視点から「できるPy」が「退屈なことはPythonにやらせよう」よりも勝るかもしれない点をいくつかあげます。

  • 書籍の値段
  • コンテンツの見た目
  • 技術的詳細よりも動くコード

書籍の値段

とてもわかりやすい比較指標です。

  • 「退屈なことはPythonにやらせよう」: 3,996 円 (税込)
  • 「できるPy」: 2,484 円 (税込)

単純な値段の比較で「退屈なことはPythonにやらせよう」が割高であると私は思いません。こちらは Python 入門についても丁寧に説明されているので分量が多くなっています。むしろ内容の充実度からすれば割安といえます。しかし、いまや数多くの Python 入門書が出版されていることから読者によっては他の書籍で Python 入門を終えていて、入門についての内容は不要という人もいるでしょう。

Python 入門が不要、且つ読者が読みたい題材を「できるPy」が扱っているのであれば、値段の安い方を選択するのもよいでしょう。

コンテンツの見た目

私は本を選ぶときに見た目を気にする方です。漫画も好きでよく読む方なのですが、絵柄の雰囲気が好みかどうかがその連載を読み続けるかどうか、時間をかけて読むかどうかに大きく影響します。そのため、自分にとって見やすいかどうかをぱらぱらページをめくりながら判断します。

もちろん知りたい内容がその本しか扱っていないのであればその本を購入するしかありません。しかし、「できるPy」のような初学者向けの本は上位互換として「退屈なことはPythonにやらせよう」がありますし、私が知らない同コンセプトの本もたくさんあるでしょう。読者が見やすい、読みやすいと感じる本を選ぶとよいと思います。

参考までにそれぞれの本の見た目を紹介します。

f:id:t2y-1979:20190525042314p:plain:w1024
退屈なことは Python にやらせよう -14 章 CSV ファイルとJSON データ-

f:id:t2y-1979:20190525042318p:plain:w1024
できるPy -029 CSV ファイルを読み込む-

余談ですが、CSV ファイルを扱うのに「退屈なことはPythonにやらせよう」は標準ライブラリの csv モジュールを、「できるPy」ではサードパーティライブラリの pandas を紹介しています。Python のインストールを Anaconda を使って行うとデフォルトで pandas もインストールされます。本書はビジネスパーソン向けということもあり、標準ライブラリよりも便利であれば、サードパーティライブラリを優先して紹介するようにしています。

また執筆はリブロワークスさんの MDBP という Atom プラグインを使いました。Markdown で原稿を書いて CSS でスタイル設定して Atom で実際の書籍のデザインに近い見た目で確認しながら行いました。とてもよくできたプラグインで公開されています。興味のある方は Atom と MDBP プラグインをインストールして試してみるとよいでしょう。

libroworks.co.jp

技術的詳細よりも動くコード

本書を執筆するにあたり、マーケティング業務に携わっている同僚にヒアリングしていたときにこんな話を聞きました。

プログラミング言語の文法がどうこうとか、仕様が云々とか、そういうのは全く興味がありません。サンプルコードの一部の処理やパラメーターを直せばよいというのさえわかれば、適当に変更して実行して、それで目的が達成できればよいです。

プログラマーにはない感覚です。目的が明確なので手段はどうでもよく、目的を達成するコードが動けばそれで満足だというのです。本書を執筆する過程でこのことは私の中で葛藤と逡巡をもたらしました。確かに勘のよい人であれば Python のコードを読んでいるうちに規則性や要点をなんとなく掴んでパラメーターや必要な箇所のみを書き換えてプログラミングできるかもしれません。プログラミングに慣れるという最初の取っ掛かりとしてはそれでよい場合もあるでしょう。

本書は実務で使えるサンプルコードを提供するという目的がありました。サンプルコードはサンプルコードでしかなく、実務というのはそれぞれの業務に特化した個別の事情や要件があり、どういう仕組みや理屈で動いているかをわからずに書き換えて通用するかという懸念があります。しかし、ちゃんと解説したところで詳細に興味がなくて読まない人たちもいるかもしれないというので困ってしまいました。どこまで詳細を説明するか、あるいは説明しないかを考えて悩みながら書いたのが本書になります。

例えば、本書の中で意図的にサンプルコードの詳細説明を省いたのが Chapter 8 の「050 PDFからテキスト抽出」の項目です。余談ですが、本書では PDF からのテキスト抽出に pdfminer.six · PyPI というライブラリを使っています。一方で「退屈なことはPythonにやらせよう」では PyPDF2 · PyPI というライブラリを使っていて異なる点の1つです。これは私の手元にあった PDF ファイルをいくつか PyPDF2 でテキスト抽出したところ、日本語の PDF ファイルのテキスト抽出ができませんでした。pdfminer.six は正常にテキスト抽出できたのでそちらを採用しました。pdfminer.six は開発者が日本人なのでテストデータとして日本語の PDF ファイルも使って検証しながら開発されたのだと推測します。

閑話休題。私の知る限り、pdfminer.six についてのドキュメントは次になります。

ドキュメントをみる限り、pdfminer.six のモジュール構造は PDF のデータ構造に大きく影響を受けているため、複数のモジュールを組み合わせて PDF からテキスト抽出する仕組みとなっています。そのため、このモジュールはどういった機能をもっているかを解説するには PDF のデータ構造について言及する必要があります。PDF ファイルからテキスト抽出するという目的に対して、PDF のデータ構造の詳細に踏み込むのは難し過ぎると私は思いました。そこで pdfminer.six のモジュール構造の説明は行わずにサンプルコードの使い方のみを説明しました。

逆の例として概要だけでも説明した章もあります。Chapter 5 の 「021 ZIPファイルに含まれるファイル名を文字化けさせずに表示する」で文字化けの概要を説明しています。もしかしたら読者層のビジネスパーソンな方々には全く興味のない話かもしれません。しかし、直接的に役に立たなくても文字化けが発生する仕組みがわかれば、なにかの機会に知識として役立つこともあるのでは?と考えてできるだけ簡潔に説明しました。

本書では実務で役立つサンプルコードを提供しつつ、その技術的詳細は最小限しか説明しないという、プログラマー視点からみるともやっとする微妙なバランスで書いています。そのため、本書で提供しているサンプルコードが読者のやりたい自動化処理に近ければ近いほどやりたいことを達成する労力を削減しやすいとも言えます。

まとめ

出版を契機に久しぶりに書店へ行ってたくさんの Python 本が置かれている棚をみました。私は10年前ぐらいから Python を学び始めました。当時と比べて、たくさんの入門本、データ分析や機械学習に関連した Python の本が敷き詰められていて驚くべき状況です。

数多ある入門本の末席の1つとして、本書をきっかけにプログラミングに慣れ親しむ人が増えて、世の中の業務のいくらかが自動化されて効率化されて誰かの役に立てば幸いです。

本書の紹介記事

本書を紹介してくれた方々のブログの記事をまとめます。

shinyorke.hatenablog.com

イベント登壇

medium.com

リファレンス

出版にあたり、共著者やアドバイスしていただいた方々やレビューをしていただいた方々の記事も紹介します。関係者の方々のおかげで出版できたことに感謝します。

xaro.hatenablog.jp

rokujyouhitoma.hatenablog.com

できる 仕事がはかどるPython自動処理 全部入り。 (「できる全部入り。」シリーズ)

できる 仕事がはかどるPython自動処理 全部入り。 (「できる全部入り。」シリーズ)

これからはじめる SQL 入門

レビューに参加した経緯で 技術評論社 さんから献本していただきました。ありがとうございます!

SQL とはなにか

本書は「データベースを操作するための言語」として、RDBMS とその実行環境として PostgreSQL を使って SQL 入門を行います。私自身、これまでおもに RDBMS を使ったシステム開発に携わってきたことから「データベース」と聞くと RDBMS を真っ先にイメージしてしまいます。本書ではローカル環境にインストールされたデータベースに対して実際に SQL を実行して、その結果を確認するといったようにインタラクティブに操作しながら学習を進めていきます。

一方で「Chapter 1 データベースと SQL」をレビューしていて私自身の「データベース」の背景についての誤解に気付いたり、SQL とはなにかと考えるよい機会となりました。もちろん本書は入門者向けに簡潔に説明されているので、データベースそのものやそのモデルの背景や歴史的経緯などについては説明されていませんし、これから SQL を学ぶ人たちが最初にそういったことを気にする必要もありません。

SQL は1980年代に生まれた技術であるそうです。古くからある技術がいまもなお使われ続けているということから多くの歴史的経緯や変遷を辿っていることが伺えます。いまも SQL が使われる分野は拡大されていて、仕様も拡張され続けています。直近で拡張された規格を3つあげると、2008年、2011年、2016年になるそうです。

例えば SQL:2016 を調べてみると、json を扱う機能であったり、Row Pattern Recognition という正規表現を使って特定の行グループを絞り込む (ログのような時系列なデータからグループ分割したものに条件指定して取り出す?) ための機能であったりが追加されています。その時代で求められる機能や要件を取り込みながら SQL は進化しています。「SQL とはなにか」という問いへの答えも、その時代でよく使われている用途に応じて変わっていくように私は思います。

本章の最後に「クラウドコンピューティングとデータベース」というコラムがあります。そのコラムではクラウド環境で利用される RDBMS の紹介に加え、DWH (データウェアハウス) のプロダクトの問い合わせにも SQL のサブセットが使えることが紹介されています。DWH も SQL を使うプロダクトの1つとして大きな領域です。ここでさらに wikipedia の Database#Examples をみると、データベースを分類する方法として3つの方法があると記載されています。

データベースを分類する方法として、1つ目は書誌、文書、統計、マルチメディアといったその内容の種類から、2つ目は会計、音楽、映画、銀行、製造業、保険といったアプリケーションの分野から、3つ目はデータベース構造やインターフェースの種類といった技術的な側面から分類されます。

これらの分類方法から wikipedia では DWH 以外にも様々なデータベースが紹介されています。記載されているもののうち、私が見聞きしたことのあるものだけを取り上げてみても以下のようなものがあります。

  • インメモリデータベース
  • クラウドデータベース
  • 分散データベース
  • ドキュメント指向データベース
  • 組込みデータベース
  • グラフデータベース
  • ナレッジベース

もちろん、関係モデルをデータ構造として扱うデータベースであれば、このような分類の違いをあまり意識することはないかもしれませんし、全く独自のクエリ言語を提供しているものもあるかもしれません。ついつい身近に使っているものだけをデータベースであるかのように錯覚してしまいがちですが、データベースには多くの種類があります。そして、その多くで SQL (またはそのサブセット) を使ってデータを操作できることは、ライブラリの再利用性、システムの相互運用性、学習コストの削減など、多くのメリットがあります。

今後も新しいデータベースが現れてくるでしょう。しかし、そのプロダクトが SQL (またはそのサブセット) を提供する限り、その分類に関係なくそのデータの操作ができます。SQL を使えるということは、IT エンジニアだけではなく、業務においてデータに関わるすべての職種、営業であったりマーケティングであったり、カスタマーサポートであったり、それ以外の多くの職種の人にとって重要なスキルとなるでしょう。

SQL を手打ちして慣れる

本書をレビューしていて過去のことを思い出しました。昔、私は SIer である基幹システムのヘルプデスクのような業務に携わっていました。お客さんから基幹システムのデータや処理についての問い合わせがあったときに調査して回答するといったような業務です。

基幹システムのデータベースにはお客さんの PC からもアクセスできる状態でした。データを直接みたいときにお客さんは Microsoft Access を使ってデータを確認していました。特定テーブルのデータをみる分にはそれで十分であったかもしれませんが、様々な調査のために複数テーブルのデータを結合し、ある条件でデータを絞り込みたいときなど、私宛に連絡がきて、私が SQL を使って該当データを調査して回答するといったことが日常でした。

Chapter 3の最後に「SQL を身に付けるには」というコラムがあります。

筆者の経験を踏まえて、1つアドバイスがあります。それは コピー&ペーストに頼らず、自分の手でタイプして体になじませる というものです。

このコラムに書かれている著者の経験に私は全く同意するところです。私自身も SQL に慣れるきっかけになったのが、日常的にお客さんから問い合わせを受けて、必要なデータを取得する SQL を手入力して調べたりしていました。

そして、SQL に慣れてきたときに運用工数を削減することを目的に、お客さんにも SQL を覚えてもらおうとしていた時期もありました。当時やり取りしていた情報システム部の担当者も自身のスキルアップを目的に SQL を学習したいと仰っていました。そこで問い合わせの回答をするときにそのデータを調査したときの SQL も添付して回答していました。そんなことを半年ぐらい行っていたのですが、残念ながらお客さんの方が SQL はやはりわからないと学習を断念してしまいました。

いま思い返すと、そのときに本書があったらまた違った結果になっていたのではないかと悔しく思います。まさに私がお客さんに提供していたのはコピー&ペーストできる SQL であって、お客さんが自分で手打ちして SQL を覚えるための仕掛けとして不十分であったことが本書を読んでいて実感した次第です。これから SQL を学習される方は、本書を読み進めながらコピー&ペーストせずに手打ちしながら SQL に慣れていくのを実践してみてください。

本文とコラムとのバランス

冒頭の「はじめ」のところで以下のように書かれています。

本書は、著者が初心に戻り「自分が SQL を学び始めたときにこのような解説書があればよかった」と思える一冊に仕上げようという思いで筆を執ったものです。

著者が IT エンジニアであるため、システム開発する上で迷うところやつまづきそうな落とし穴、有用な情報を「コラム」として所々で補足しています。SQL を学ぶという視点からは確かに副次的な情報であるため、そういった情報は本文に含めずにコラムとしてうまくバランスをとっているように思います。というのは、本書の読者は IT エンジニアだけではないと想定しているのだと思います。私がレビューしていて、こういった情報もあった方が良いのではないかと指摘したこと・しようと思ったことの大半がコラムで簡潔にまとめられているので親切な入門書になっていると思います。

コラムの目次がないのはもったいないと思ったので以下にまとめてみました。

Chapter 1 データベースとSQL

Chapter 2 PostgreSQL環境の準備

  • Column: 環境構築やツールに関する補足情報 ・・・ 42

Chapter 3 データの取得と絞り込み(SELECT)

  • Column: ORDER BY 句を省略した場合の並び順 ・・・ 57
  • Column: OFFSET 句の代替 ・・・ 82
  • Column: SQL を身に付けるには・・・ 84

Chapter 4 データの作成・変更(INSERT,UPDATE,DELETE)

  • Column: CRUDSQL ・・・ 102

Chapter 5 データ型

  • Column: 文字型をどう使い分けるか? ・・・ 114
  • Column: 符号なし整数を扱うデータ型は? ・・・ 118
  • Column: シーケンスの重複を自動で防ぐには? ・・・ 125
  • Column: もっと複雑な配列も扱える ・・・ 135
  • Column: そのほかのデータ型 ・・・ 138
  • Column: JSON 型の使いどころ ・・・ 143

Chapter 8 テーブルの結合

  • Column: AS も省略できる ・・・ 205

Chapter 9 サブクエリ

  • Column: IN と EXISTS の違い、JOIN への書き換え ・・・ 240

Chapter 10 一歩進んだ SQL

  • Column: 全文検索システム ・・・ 251
  • Column: WHERE 句で関数や演算子を使う際の注意 ・・・ 252
  • Column: WITH 句を使った SELECT 文 ・・・ 263
  • Column: ほかの RDBMS での UPSERT ・・・ 266
  • Column: オートコミット ・・・ 273

Chapter 11 データベースとテーブルの操作

  • Column: 複数の制約を組み合わせる ・・・ 287
  • Column: ほかの RDBMS では列の場所を指定できる? ・・・ 287
  • Column: VIEW の変更 ・・・ 314
  • Column: データベースと日本語 ・・・ 315

まとめ

本書は SQL を段階的に学んでいく上で丁寧な解説と構成になっています。そのため、どちらかと言えばシステム開発に直接関わらない人向けにとってよい入門になると思います。冒頭でも述べた通り、データベースの重要性や SQL の応用範囲は広がる一方です。本書がデータを扱うすべての職種の人にとって SQL を学ぶきっかけになればと思います。

これからはじめる SQL入門

これからはじめる SQL入門

達人プログラマーの新装版がでたので読み直してみた

オーム社 さんから献本して頂きました。ありがとうございます!

原著は1999年10月20日に出版されたそうです。このブログ記事を書いているのが2016年11月なので17年前に書かれたことになります。元の出版社であるピアソンエデュケーション社が日本から撤退したことによりオーム社さんに移管され、その機会に翻訳も全面的に見直されたようです。

本書を読む動機付け

変化の速い IT 業界において17年前に書かれた本をいま読む価値があるのか?この問いに答えるのは難しいです。

開発プロジェクトという日々の業務では、様々な要件や移り変わる状況の中で何かしら制約がありつつも判断を下さなければなりません。それはアーキテクチャの策定であったり、開発方法論の実践だったり、個々の技術の選定だったりします。そこで判断を下し、そのときの判断が正しかったのかどうか、本当の意味でその正否はその後の歴史でしか分かりません。さらに何をもって正否と判断するのか。この正否の判断自体も多様な価値観で行われるものですが、達人プログラマーの原題である The Pragmatic ProgrammerPragmatic (実践的) という意味を借りて、広く浸透している考え方やうまく運用がまわっている仕組みをここでは正しいとします。その考え方が本当に広く浸透しているのか、その仕組みが多くの現場で使われているのか、それを私が保証することはできませんが、私の周りで見聞きする分にはそうみえるということで話を進めていきます。

閑話休題。本書を読むことで17年前に書かれた考え方や仕組みが 実践的に 正しかったのかどうかの一端を知ることができます。ともすれば、IT 業界というのは新しい技術や方法論が毎年たくさん出てきて、生き残るものもあれば消えていくものもまた多いです。そんな業界で17年も生き残っているものがあるとしたら、それは普遍的なものだと言ってしまっても過言ではないかもしれません。目新しいものに注意を取られがちな日常で何が本質かを考えたときにその基礎を教えてくれる、歴史を経た 達人プログラマー は単にプラクティスをまとめた本ではなくなっているように私は思います。

ある程度、業務でプログラマー経験のある方は私のような読み方もできますが、一方で経験の少ない若い方にとってはどうでしょうか。もちろん若い方へもお勧めしますが、本書の中で特定技術を例として紹介されているものの中には、いまは廃れてしまって現状にそぐわないものもあります。そのため、若い方が聞いたことのない特定技術が出てきたときは、その技術については自分で調べ直してみることが必要になります。訳注で補足をしている箇所もいくつかあるのであわせて確認してみてください。

達人プログラマーの所感

私が印象に残った節についていくつか抜粋しながら簡単に所感を書いていきます。

4 十分によいソフトウェア

この節ではソフトウェアのリリースをどのタイミングで行うかといった考え方を書いています。内容自体はプロジェクトマネージャーやプロダクトマネージャー向けに書かれているものですが、私は読んでいて、一般の開発者がいつプルリクエスト (以下PR) を送るかというタイミングを考えるときに似ているように思いました。

コードレビューをするのが当たり前のワークフローになりつつある昨今、どのタイミングで PR を送るか悩む人もいると思います。私は自分の中で8割ぐらいの品質になったら気軽に PR を送る方です。言わば、いくつかツッコミ所を残した状態で PR を送っています。その理由は次になります。

  • その PR の課題に対する解決策として設計や考え方があっているかどうかを判断してもらう
  • 全体からすると些細な内容でどう対応するかを自分の中でも迷いがあるので意見がほしい
  • 自分が書いたコードの意図が他人に伝わるかどうかをみてみる

そしてレビューしてもらって設計や考え方があっていないのであれば、やはり気軽に PR を取りやめて再設計します。どんなに時間をかけて品質をあげても他人の視点をもつことはできません。もちろん自分の中ではこの設計方針がベストだと考えて PR を送るわけですが、それまで自分が考えていなかった懸念や概念を他人から与えられることによって、他のやり方が妥当だと思うこともあります。

せっかく書いたコードを捨てるというのは悔しい行為だと思います。時間をかければかけるほど、サンクコストも気になってしまいます。時間 (コスト) のかかる機能開発だと進捗を小まめにレビューしてもらって手戻りを少なくする工夫をすると良いと思います。

7 二重化の過ち

いわゆる DRY原則 を紹介しながら、いろんな状況で二重化が起きることを説明しています。システム開発をしていて最も身近な問題の1つと言っても良いかもしれません。

プログラミングを始めた人にとって、おそらく初期段階でそこそこの規模のアプリケーションを実装していると実感すると思います。引き継いだ既存のアプリケーションの品質がよくないとそういった重複コードに悩むこともあります。

原則はあくまで原則でしかなく、結局のところその場その状況において何が最善であるかを選択する必要がでてきます。メリット/デメリットを考慮した上でやむを得ず二重化するという判断も現実にはあるでしょうが、二重化には多くの弊害があるという事実を知っておくことが重要です。そして、そうならないようにどうすれば良いかを考えていくことも重要です。そういう考え方をしているうちに、言語機能、設計手法や開発方法論で解決しようとしていることへの理解にもつながっていくように私は思います。

8 直交性

ときどき聞く言葉なのに私はちゃんと定義を把握していなかったので再学習しました。

「直交性」とは幾何学の分野から拝借してきた用語です。(中略)。この用語はコンピューティングの分野では、ある種の独立性、あるいは分離性を表しています。2つ以上のものごとで、片方を変更しても他方に影響を与えない場合、それらは直交していると呼ぶわけです。

直交性は DRY の原則とも密接に関係しています。またこの節では設計、ツール、コーディング、テスト、ドキュメントにおいても直交性の概念が適用できると紹介しています。

優れたフレームワークやライブラリ、良いプラクティスが共有されやすい昨今だと、直交性は当たり前過ぎてあまり意識することはないかもしれません。そういった背景にある概念を学ぶ良い機会にも思いました。

12 専用の言語

いまの言葉にすると ドメイン固有言語 (以下DSL) に相当すると思います。Tips としては以下のように書かれています。

問題領域に近いところでプログラミングを行うこと

この概念自体は適切だと思いますが、その事例の1つとしてミニ言語を実装する事例が紹介されています。またそれが必要な根拠として sendmail の設定は複雑なので DSL により制御を容易にする例を紹介しています。

達人プログラマーに書かれているほとんどの内容は、小さい開発プロジェクトにも大きい開発プロジェクトにも両方適用できるように書かれていますが、DSL に関して私は慎重派なので反対の立場にたってその理由を書いてみます。

もちろん巨大で複雑なプロジェクトや十分に成熟したアプリケーションに対して DSL が大きな価値を提供する場面があることは私も同意します。あえて反論しようと思ったのは DSL を開発するデメリットもあるからです。

  • 開発者にとって DSL の開発・保守コストがかかる
  • ユーザーにとって DSL の学習コストがかかる
  • ユーザーにとって DSL で書かれた機能や設定の保守コストがかかる
  • アプリケーション本体の機能と DSL が提供する機能の間にズレが生じる可能性がある
  • アプリケーション本体と DSL の依存関係を管理する必要がある

ざっと思いついたものをあげてみました。自分が取り組んでいる開発プロジェクトでこれらのコストを負担しても DSL を使うメリットがある場合は取り組んでも良いでしょう。逆にこれらのコストを考えずに安易に DSL を使うと将来の技術的負債となることもあると思います。

14 プレインテキストの威力

シンプル且つ意味深なように思えてなぜか印象に残りました。

知識はプレインテキストに保存すること

テキストの利点として以下をあげています。

  • 透明性が保証される
  • さまざまな活用ができる
  • テストが容易になる

いまどきの利点をさらにあげるとバージョン管理システムで差分管理しやすいという点も追加したいです。

これはこれで言っていることは正しいのですが、言わばテキストなら後から何とでもできるということでしかありません。もし特定用途に使いたいとしたらデータを正規化して扱いたくなるので、テキストデータは逆に曖昧で扱いにくいものです。例えば、HTTP/2 でバイナリープロトコルを採用した理由として、解析が容易でエラーチェックが厳密である利点があげられています。

18 デバッグ

私はこの節が好きです。残念ながらデバッグの手法は昔と比べてほとんど進歩がないように思います。大きく分けると以下の2つでしょう。

もちろん言語処理系の型システムが強力になったり、ツール/ライブラリが拡張されてより便利になったりはしています。しかし、エラーが発生して人間がソースコードを読みながら解析して原因を追求していくという行為自体はほとんど変わっていません。この節ではそのための心構えや戦略が丁寧に説かれています。

1つ私も過去にやっていた実践的な手法を紹介します。

問題の原因を探し出すための非常に簡単で効果的なテクニックとして、「誰かに説明する」という手法があります。

これはデバッグに限らず、コードレビューをレビューツール上だけでなく、レビューアに対面で説明するときにも有効です。説明しているうちに自分でもっと良い方法を思いついたりすることもあります。おそらく人間の思考として、ものごとを多面的にみる上でアウトプットの方法を変える手法が強力なんだと思います。

21 契約による設計

この節はどうなんでしょうね?あらかじめ要件や仕様を厳密にしやすい業務系ではこういった設計が定着しているのでしょうか?私は経験がなくて実践的にこの手法が使われているのかどうか知りません。

Web 業界ではまだまだここまで厳格な設計手法は定着していないように私は思います。また別の節にある表明プログラミングもそうですが、厳密さを保証するためのオーバーヘッド (実行効率や保守性など) もかかることから敬遠されがちなところもあると思います。

そう思ってはいるものの、最近契約による設計に関する記事をみかけたので以下を紹介しておきます。

28 時間的な結合

マルチスレッドを用いた並列処理について説明されています。さすがにこの節の内容や例は古くなってしまっているように感じます。非同期/並行処理 *1 は、プログラミング、システム設計、いろんな状況で本質的に難しい課題です。

この節ではワークフロー、アーキテクチャ、設計、インターフェースについて説明されていますが、内容がいまどきの Web 開発の問題とはややあっていないかもしれません。もちろん本書は Web 業界向けに書かれたものではないと思いますが、それでも昨今のクラウド化や開発方法論の変化との乖離により違和感があるのは仕方ないように思います。

1つだけ補足しておくと、昨今はそれぞれのプログラミング言語が非同期/並行処理をサポートする機能を提供していたりします。例えば、Python では asyncio というライブラリが標準で提供されています。そのライブラリはプログラマーが陥りがちな落とし穴を回避してくれます。そのため、自分で一から考えて作り込むのではなく、その言語やライブラリが提供する機能や仕組みに沿って開発するのがいまどきのプラクティスになるのではないかと思います。

31 偶発的プログラミング

本書を読みながらツイートしていて最も Impressions が高かったのがこの節でした。開発者が自分で実装しているコードをあまり理解せずに実装を進めてしまい、あるとき動かなくなってしまうときの背景や状況について説かれています。

いまはプログラミングを学ぶドキュメントがインターネット上にたくさんあっても、おそらくこの問題は昔よりも現在の方が起こりがちではないかと想像します。そして私自身も程度の違いはあれど実際にやっていると思います。誰かが作った優れたフレームワークやライブラリもこのことを助長します。

さらに何らかの問題や分からないことがあっても、大抵は stackoverflowqiita がその解決策として検索にヒットします。ほとんどの場合において、そこに書かれている内容を試せばうまくいくと思います。コードをそのままコピペする人もいるでしょう。それを繰り返した結果、簡単に偶発的プログラミングに陥ってしまいます。

以前ドワンゴの川上さんが仰っていた 膨大な数の二流のウェブエンジニア という言葉がずっと頭の中に残っています。

36 要求の落とし穴

この節も私は好きです。

要求は拾い集めるものではなく、掘り起こすものである

この言葉は的を射ています。要件定義はスキルの有無や頭の回転の速さに関係なく、その人の当事者意識の在り方に影響すると私は過去の経験則から考えています。つまり話を取りまとめて形式化するのが上手なことと、適切な要件を掘り起こすこととは違うということです。その当事者意識とはどうやって得られるのかの最たることも Tips に書かれています。

ユーザーの視点に立つには、ユーザーと働くこと

実際に業務でできるかと言うと難しい状況もあります。開発者がユーザーの気持ちを理解することが重要なのは誰しも経験から分かってくるように思います。ドッグフードを食べるとも言われたりしますが、可能であれば自分が開発しているアプリケーションやシステムを自分で積極的に使うことが最初の一歩になるはずです。

38 準備ができるまでは

この節では自分の勘と経験で直感的におかしいと感じたら一旦立ち止まることの大事さについて書かれています。そして、そのときにプロトタイピングがその不安材料を洗い出すことに役立つともあります。

ちょっと精神的な話になるかもしれませんが、私は無意識に考えることを意識的に活用するときがあります。難しいバグに悩まされている最中、帰ってきて寝て起きたら手がかりを閃いたという経験がある人も多いと思います。

未経験のものごとに取り組んでいると心理的に不安に思ってしまうのは仕方ないことです。そのときは分からなくてもずっと考え続けていることで、あるとき閃くんじゃないかと楽観的に信じ続けることが良い方向に働くときもあります。勉強するときもこのことは役に立ちます。いま分からなくてもずっと勉強しているとあるとき分かるんじゃないかと諦めてしまわない根拠に使えます。

誰しも分からないという状態は辛いです。その分からないストレスを下げる工夫にもなるのではないかと私は思います。

読みながらそのとき思ったこと・考えたこと

本書を読みながらツイートした内容です。余談ですが、先の所感を紹介する節を選定するときに自分のツイート分析から多くの人が関心をもってそうなものも考慮しました。ハッシュタグをつけておけば良かったと後で後悔しました (´・ω・`)

リファレンス

新装版 達人プログラマー 職人から名匠への道

新装版 達人プログラマー 職人から名匠への道

*1:この節では並列と並行の詳細には触れないとあるので、ここでも並列と並行の違いについては触れません。私の感覚的なもので非同期と並行をセットで考えるのでそのように書いています

考える生き方

極東ブログ の著者の著書を読んだ。

それまでブログの存在は知っていたし、twitter 経由でリンクから記事を読んだことも何度かあった。著者がアルファブロガーの1人だということも知っていた。著者のブログである記事の内容を覚えている。いま見返すと、2年前の記事だった。2年前の記事を何となく読んだだけで未だに記憶に留めているというのは、よくよく考えてみるとすごいことだ。

引用が長くなるので結論だけ紹介するが、この結びが強く印象に残っている。

そういう無名の社会人が困難な状況のなかで「公」を支えてくれるおかげで、私たちは生きていけるわけです。みなさんの、誰かもそうなります。

 だから、いつも思うのです、ありがとう。

4月から新社会人になる皆さんへ: 極東ブログ

本書は何か

たぶん何でもないの只の自伝だと著者なら言うかもしれない。著者は自分の生き方や過去を「からっぽの人生」だったと振り返っているが、本書を読む限り、そんな自虐するようなものでもないように思えた。普通の人の、普通の人生も、端からみると全然普通じゃないというのを端的に表しているのかもしれない。

著者が「からっぽ」だというのは、世の中で理想とされるようなレールに則った人生ではなかったということを指しているのかもしれない。それは社会的に承認されることのない生き方だったのかもしれない。著者の年齢が55歳とあるので、いまよりもずっとそういったレールから外れた生き方に理解がなかったり、厳しかった時代だと思う。

著者のブログのファンならば興味深く読めると思う。私は逆だけど、本書を読んで著者の人となりを知って rss reader に追加した方だ。ブログでもそうだけど、読みやすく、ユーモアを交え、押し付けがましくない著者の文体に好感がもてる。著者は否定しても、アルファブロガーというのはやはりちょっと普通じゃないと思うので、普通の人が普通の生きるための参考になるかは正直分からない。でも、誰彼もその人の人生であって、それが普通か普通でないかは、さして意味はないというのを考えさせられる気もする。

テーマが多い

著者の半生を綴ったものだからテーマが多い。仕事、家族、恋愛、難病、学問、社会、文化、人生、、、色んなことが書いてある。その分、著者の主張のようなものがあるわけではない。ブログの延長と言えばそうかもしれないが、本書は誰かのための、何かのためのものという目的では書かれていない。読者が読んで何かを考えるきっかけになるようなものだと思う。

私の中では、最もおもしろかったのは、第5章の「勉強して考えたこと」でした。著者の勉強に対する考え方や向き合ってきた姿勢が読み取れて興味深かった。

勉強する意味

著者の勉強する意味とはこうらしい。

私は、楽しいからだと思う。もちろん、私がそういうタイプの人だからというのはある。
(中略)
知るということ、それ自体が楽しい。子どものころは、ほとんどの人がそうだった。それをいくつになっても維持していくようにするのが勉強ではないかと思う。

著者と自分の共通する点が1つある。それは職を転々と変えてきたところだ。一般的に職を変えることはあまり良いことではないだろう。ただ、考えるという点において環境を変えるというのは大きな影響力をもつように思う。

考える習慣を持たない人に共通する問題点として 「無知の知」 ならぬ 「無知の無知」 があります。 自分が分かってないことを分かってない、 自分の浅慮では到底及ばない領域があることが全く想像できない、 いわば 「井の中の蛙」 状態ですね。

仙石浩明の日記: 「無知の無知」への 3ステップ

また別のブログの引用だけど、またしても新社会人向けに書かれた記事だw、なんだ?そんなことも分かってないのか自分、、、という気分だ。

長く同じ環境にいると人間は慣れる。慣れると、考えずに行動できるようになる。それ自体が悪いわけではないけど、人間は楽をする生きものだから無意識にものごとを深く考えないようになってしまうことがあるように思う。そのときにこの「無知の無知」が起こる気がする。私の場合、転職を繰り返すうちに、前の職場で通用した知識やスキルが次の職場では全く通用しないことが起こる。そんなときに強く実感する。自分は何も分かってなかったんだなぁと。

私にとっての、勉強する意味は2つある。

一つは、勉強してないと、この先生き残れないんじゃないかという不安がある。周りの人たちも当たり前のように勉強しているのを見てきたのでそこに疑問はないし、勉強せずに活躍している人もやはり見たことがない。もう一つは、分からないと悔しい。気持ち悪い。恥ずかしい。そういった負の感情へのコンプレックスがある。

著者の知りたいという動機とは純粋度が違う。違う分、業務の勉強なんかには向くけど、自然科学とか、興味がないものの勉強には食指が動かない。

国際性の中のエピソード

著者の大学時代の、留学生とのやり取りのエピソードでおもしろいものがあった。

「NO」と言えるのは、人間が対等なときだ。人間が人間であるなら、違う考えをもって表明するときは、「NO」という。

気さくな友だちには NO と言えても、そうじゃない知り合いとか、同僚とか、そういう人たちに NO と言いにくかったりするのは、そういった背景もあるんだなぁと省みてしまった。ここで言う対等というのは立場的なものじゃなくて人間関係なようにも思う。

リベラル・アーツという概念

この節がすごくおもしろかった。この節を読むだけで純粋に勉強する楽しみというのはこういうことなんだなと分かった気になる。

メカニカル・アーツの対立語としての wikipedia:リベラル・アーツ について述べている。この wikipedia の説明もおそらくは正しいのだろうけど、本書と比べて、その時代の背景や文化的な側面があまり書かれていない。辞書を引けばその定義が書いてある。そういうものだけど、何だかしっくりこない。

本書では、まず「リベラル」とは自由市民を指していて、自由市民とは、奴隷とは、枝芸とは、そういったあれを知るためにはこれを知る必要があって、背景が分かった上で最終的にはこうなんですと展開している。それはほんの上辺でしかないけど、体系的に学ぶというのはそういうことかとも思う。そして、それがおもしろいから勉強するんだなということも分かる。決して wikipedia を作るために勉強するわけではないんだ。

リベラル・アーツが何ものかが分かったところでその必要性を説く。きっと著者の講義を受けられるとしたらこんな感じで勉強する楽しさを教えてくれるように思う。

また余談だけど、art という単語から想像する日本語は「芸術」だろう。実際、その訳語が第一義だから正しいのだけど、辞書を引くと art という単語には「技術」や「技能」といった訳語も出てくる。ここで言う、リベラル・アーツの art というのは、徒弟訓練で学ぶ技能の体系のことを指すらしい。

"The Art of Computer Programming", "The Art of UNIX Programming" など、Art of と銘を打つコンピューター関係の書籍はちらほらある。それらのイメージを単純に私は「芸術」と捉えていたのだけど、なんだ技術体系の書籍だという見方もあるのかと再発見した次第だ。

終わりに

そして、やはり結びの言葉も良い。

こう言ってもいいのかもしれない。人生の敗北者であっても、貧しい生活でも、学ぶことで人生は豊かになる。

希望はある。

考える生き方

考える生き方

パーフェクト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 サポーターズは、自分はこわくないですとみんな言いますが、本当はこわいと思います。

リーダブルコード

うろ覚えですが、学生の頃、プログラミングの勉強をしていて、どこかのサイトに 1000 行以上のコードを書いてみるとその言語のプログラミングの雰囲気が分かるといった内容が書いてあったような気がします。本書は特定の言語に依存した内容ではありませんが、少しは言語依存の内容もあります。

言語仕様を学び、コーディングに慣れてきたら、その次に気になるのが設計だったり、抽象化だったり、人間にとって自然な概念や分かりやすさを表現したくなってきます。本書はそういった人間のための「リーダブルコード」を書くためのコツやその具体例が簡潔にまとめられています。リーダブルコードを書こうと意識するのもまた違ったプログラミングの醍醐味に触れる機会となるでしょう。

個人的におもしろかったところ、気付きがあったところを抜粋して紹介します。

名前重要、超重要!

面倒だからと言って汎用的な名前に逃げない

tmp・retval・foo のような名前をつけるのは、「名前のことなんて考えていません」と言っているようなものだ。このような「空虚な名前」をつけるのではなく、エンティティの値や目的を表した名前を選ぼう。

がーん。

出鼻からくじかれた気分になります。普段から気を付けていることですが、たまに使ってるときもあったりします。私は名前を付けるのが下手で、リファクタリングで名前を変えたりもちょくちょくします。まさに名前付けに王道なしって気がしました。

範囲指定の名前の付け方

これは英語ネイティブではない日本人だと分からない感覚です。素直にそのまま従うと良さそうです。本書では、実際に境界値を含む含まないを図解で解説しているのでさらに分かりやすいです。

  • 限界値を含めるときは min と max を使う
  • 範囲を指定するときは first と last を使う
  • 含有/排他的範囲には begin と end を使う
式を分割する変数の意義
  • 説明変数: 式の意味を説明する変数
  • 要約変数: 式を説明する必要がない場合でも概念を簡潔に表現する変数

この概念が私にとっては目から鱗でした。

式や関数呼び出しを、そのまま関数の引数に渡したり、戻り値として返すコードをよく書いたりしていました。ローカル変数に代入すべきかどうかを迷うときもよくあったのですが、説明したり要約したりする必要があるかどうかで1つの判断基準ができました。

スコープの意識

スコープが小さければ、短い名前でも良い

以前「名前に迷ったら長い名前を書けば良い」というプラクティスを実践していて、何でも長い名前を付けていたときがありました。

ほんの数行から十数行程度の小さなユーティリティ関数なら、関数名で分かりやすさを表現すれば、内部のローカル変数は短い名前でも良いように私も同意します。Python なら PEP8 を守って 80 行文字以内に収めようとするので、以下のようなコードは状況により名前を変えたりもします。

match = re.search(REGULAR_EXPRESSION, text)
if match:
    ...

m = re.search(REGULAR_EXPRESSION, description_to_be_explained_in_detail)
if m:
    ...
Javascript の「プライベート」変数のサンプル

スコープは、言語によって違うので、その言語の仕様やイディオムを理解しておく必要があります。慣れた言語のスコープが頭の中に残ってしまうので、Javascriptクロージャーを用いたサンプルコードの奇妙さからスコープの配慮の重要性が分かりやすかったです。こういったイディオムは、より深く言語を理解する上での取っ掛かりになりますね。

var submit_form = (function () {
    var submitted = false;
    return function (form_name) {
        if (submitted) {
            return ; // 二重投稿禁止
        }
        ...
        submitted = true;
    };
}());

コメントの書き方

私が唯一意識しているコメントの書き方は「意図を表す内容を書く」ということだけでした。本書では、それ以外にも良さそうな実例が紹介されています。

コメントを書かないモチベーション

優れたコード > ひどいコード + 優れたコメント

おもしろい表現です。ひどいコードを説明するのに労力をかけるなら、ちゃんとしたコードを書きなさいという教訓ですね。

ひどいコードにはマーキングする

さらにコードの修正を促すコメントを付けることも恥ずかしがってはいけないとあります。確かに他人のコードを修正するのはやや躊躇してしまうこともあるので、堂々とコードが汚いことを認めるのが良さそうです。よく使う記法です。XXX って危険という意味だったのですね、私は知りませんでした。

記法 典型的な意味
TODO あとで手をつける
FIXME 既知の不具合があるコード
HACK あまりキレイじゃない解決策
XXX 危険!大きな問題がある
分からなかったらググれ

以下のコメントもおもしろいです。

// ベクタのメモリを解放する (「STL swap 技法」で検索してみよう)
vector().swap(data);

まとめ

名前を分かりやすくすること、コードをキレイに保つこと。

リーダブルコードの最初の取っ掛かりです。たくさんコードを書いて、失敗して、工夫して、もっと良い方法を学ぶ。本書は、シンプルなテクニックを紹介していますが、多くのプログラマーの試行錯誤の上で生き残った実践的なテクニックだと思います。知っていることであっても、普段から注意してコーディングしているか、実践しているかを考えるきっかけにも良いと思います。