できる 仕事がはかどるPython自動処理 全部入り。の第5刷が決定しました

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

t2y.hatenablog.jp

おかげさまでまだコツコツと売れているようです。本書を購入していただいた方々、ありがとうございます。

このたび、第5刷の重版が決定しました。

【第5刷】7月17日(金)重版出来

book.impress.co.jp

出版してから1年ほどが経過し、今回の重版で発行部数が1万部を突破しました!

ビジネスPythonを学ぶ会の活動と slack の紹介

本書の読者サポートも兼ねて私が運営しているコミュニティがあります。

新型コロナウイルス感染防止の意図もあり、勉強会をオンラインで開催するようにしました。世の中的にオンラインで勉強会に参加することが一般化したせいか、オフラインで開催していたときよりも多くの人が参加してくれるようになりました。このままずっとオンライン勉強会を継続しようかと最近は考えています。

このコミュニティは難しいことを扱うわけではなく、またすぐに業務で役に立たないかもしれませんが、コミュニティとして長く続けられることだけを目標にしています。Python で困ったときに気軽に相談できる場になればいいなと思います。

bizpy.connpass.com

本書の対象読者と同様、Python 入門を終えたばかり初学者、さらに IT エンジニアではない次の方々を対象としています。

いまのところ、月1回の勉強会を開くことを予定しています。オンラインでのコミュニケーションが取れるように bizpy.slack.com を開設しています。興味のある方はこちらの 招待リンク からご参加ください。

コミュニティの趣旨の詳細については次のスライドに整理したので興味がある方はこちらも参考にしてください。

docs.google.com

次回からは Web API/SDK を扱う勉強会を何回かのシリーズで開催する予定です。本書では扱っていませんが、勉強会の参加者から Twilio というサービスがあると教えていただいたので次回は Twillo の API/SDK を取り扱います。

直接、本書にない内容であっても同じレベル・カテゴリに属する話題であれば勉強会の中で積極的に扱っていくつもりです。コミュニティなのでそういった参加者とのやり取り、そのとき流行っているもの、世の中の変化にあわせた話題などを提供できればいいなと思います。

私自身、普段の業務で扱っているビジネスや技術分野はとても狭いので参加者からそういった情報を教えてもらえることはありがたいものです。普段、接点がない人たちの視点や話からお互いに示唆をうけるような取り組みをさらに進められればと思います。

プログラミングできる人が増えること自体がいまの世の中にとっては良いことだろうと私は信じているので今後もそういった取り組みへのサポートをしていきます。

まとめ

プログラミングのスキルを身につけるには、一般論として、たくさんのコードを読んでたくさんのコードを書くことで習得できます。一朝一夕ではならず、日々の生活や業務に無関係だとなかなか行動に移すのが難しい場合もあるでしょう。

勉強会などは直接のスキルアップには結びつかないものの、スキルを学ぶきっかけ作りとして役に立つ場合があります。私自身、技術コミュニティへ参加しながらプログラミングを学んできました。

本書の出版をきっかけに「ビジネス x プログラミング」というテーマを考えながら1年ほど経過しました。しかし、まだまだ試行錯誤で今後の展望もみえていません。それでも勉強会を継続してきたことで少しずつ改善がみえ、ノウハウやコンテンツも積み上がってきました。いまはこのサイクルを継続することが次につながるように捉えています。

リファレンス

t2y.hatenablog.jp

Pythonハッカーガイドブック -達人が教えるデプロイ、スケーラビリティ、テストのコツ-

マイナビ出版 さんから献本していただきました。ありがとうございます!

book.mynavi.jp

本書は初学者向けではなく、Python である程度プログラミングができるようになった後にスキルアップするための、中級者向けの書籍になります。昨今の流行りから言うと、例えば、データサイエンスに関わるデータサイエンティストやプログラマーがより実践的で効率のよいプログラムを書くことや、Python を使ったプロジェクトをうまくまわすための手助けになるでしょう。

本書では Python プログラミングでよく知られたプラクティス、ドキュメント作成、パッケージング、テスト、パフォーマンスの最適化とアーキテクチャなどが紹介されています。

著者の Julien Danjou 氏は、OpenStack という、巨大な Python プロジェクトのコントリビューターであり、チームリーダーを務めているようです。冒頭の「はじめに」のところでは OpenStack は900万行もの Python のコードで構成されているとあります。

単なる Python のテクニックのみを集めた書籍というわけではなく、著者の経験による、(ある程度の規模の) Python プロジェクトを通して Python をどのように活用してきたか、あるいは活用すればよいかの示唆を与えるような、目次からもそういう意図が伺えます。

本書からいくつか抜粋して私の興味・関心のある章を紹介します。私はこれまで何冊か Python の書籍を読んできましたが、その中で他の書籍ではあまりみたことがない内容を取り上げてみます。

第4章 タイムスタンプとタイムゾーンの処理

頻繁に扱う処理ではないものの、日時間の差を求めたいときなど、タイムゾーンはどうやって扱うのだったかな?と検索することが私は稀によくあります。もう同じことを10回以上は調べた気がします。本章でタイムゾーンをどう扱えばいいのかを学ぶことができます。

余談ですが、日時を扱う処理は本質的に複雑です。

自分が言えるのは、日時処理はそもそも本質的に複雑だという経験です。ざっと思いつくだけでも日時処理が複雑になる要因として次のような思いつきます。

  • そもそも一貫性がない(月ごとに日数が異なる)
  • 様々な基数(12進数、60進数、7進数?(曜日))
  • 何の法則もない祝日
  • 何の法則もない年号
  • 複雑さに輪をかけるタイムゾーン
  • 複雑さに輪をかけるうるう年
  • 複雑さに輪をかける夏時間

Java8の日付および時刻処理(Date/Time API(JSR-310))の紹介と利用指針 | ありえるえりあ

プログラミングを教えるときなど、本質的複雑さとはなんぞやを説明するのに日時データを扱う処理をイメージするとわかりやすいので説明にもよく引用します。業務においても、こういった法則性もなく、なんらかの背景により条件が課せられるといったことは頻繁にあります。

閑話休題。まず Python の標準ライブラリで datetime オブジェクトを取得するとタイムゾーンの情報はもっていません。

>>> from datetime import datetime
>>> datetime.utcnow().tzinfo is None
True

タイムゾーンの情報を扱うには python-dateutil というサードパーティのライブラリを追加でインストールして使うのが一般的だと思います。

>>> from dateutil import tz
>>> timezone = tz.gettz('Asia/Tokyo')
>>> datetime.now(tz=timezone)
datetime.datetime(2020, 5, 29, 20, 31, 26, 444650, tzinfo=tzfile('/usr/share/zoneinfo/Asia/Tokyo'))

例えば、サマータイムが終了するときはローカル時間では同じ時間が2回やってきます。1回目の時刻と2回目の同じ時刻を判別できるように fold という属性が Python 3.6 以降で追加されています。うろ覚えで見かけた気はしますが、自分でコードを書いたことがなかったので今回初めて fold を使ったコードを学びました。

>>> berlin = tz.gettz('Europe/Berlin')
>>> confusing = datetime(2020, 10, 25, 2, tzinfo=berlin)
>>> confusing.replace(fold=0).astimezone(timezone)
datetime.datetime(2020, 10, 25, 9, 0, tzinfo=tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
>>> confusing.replace(fold=1).astimezone(timezone)
datetime.datetime(2020, 10, 25, 10, 0, tzinfo=tzfile('/usr/share/zoneinfo/Asia/Tokyo'))

日本はサマータイムがないのでこういった処理を実装することは滅多にないでしょうが、知っておくと海外の開発者と一緒にお仕事をするときに役立つかもしれません。

第9章 AST、HYLisp ライクな属性

私の記憶では ast モジュール を説明している Python の書籍は滅多にありません。私自身、過去に ast モジュールの使い方がわからなくて調べたときがありました。

公式ドキュメントを読んでもモジュール内のクラスやメソッドの説明しか書いていないため、どういった用途に使えるものかよくわからなかったからです。

幅広い分野で利用されている Python の用途からすると、ast モジュールを使う機会は相対的に限定的であると言わざるをえないでしょう。したがって ast モジュールを説明する書籍は稀だと私は思います。

第9章の冒頭では、まさに PythonAST は十分に文書化されていないという背景から本章を設けたことが述べられています。そのため、本章は ast モジュールの使い方から Python の AST オブジェクトの解説も行われています。そして、ast モジュールの典型的な実用例としてコードの静的解析ツールである flake8 を拡張するサンプルコードが紹介されています。

最後に Hy という Python の AST を使って実装された Lisp ライクな言語を紹介しています。著者は Lisp 愛好家のようです。

github.com

Hy の話題は本当におもしろい取り組みだと私は考えていて、この言語は PythonDSL (マクロ) をどう実装するかの、実際に動く事例の1つであるからです。

$ pip install hy
$ hy
hy 0.18.0 using CPython(default) 3.8.1 on Linux
=> (defn hello [name]
... (print (% "Hello %s!" name)))
=> (hello "t2y")
Hello t2y!

Hy の紹介を終えた後にその開発者である Paul Tagliamonte 氏のインタビューも掲載されています。このインタビューも示唆に富んでいて私は楽しめました。Python の AST はプライベートでもパブリックでもない曖昧な仕様であること、Python の実装やバージョン間によって AST が異なる可能性があること、HyPython の相互運用性は双方向にインポートできることから驚くほど高いことなどが述べられています。

現実の業務では、ast モジュールや Python の AST を操作するようなことは、静的解析やコード生成、特殊な事情における最適化など、ほとんどの開発者にはあまり関心のない話題かもしれません。しかし、言語処理系やプログラミングのことをより深く学ぶにはよい取っ掛かりでおもしろい題材になると私は思います。

本書は全体として実践的な内容をまとめたものです。しかし第9章だけは、どちらかと言うと、ハックを刺激する内容になっています。そのため、プログラミングが好きな開発者向けに楽しめる章ではないかと私は思います。

第11章 スケーリングとアーキテクチャ

ページ数は10ページ強と少ないのですが、アーキテクチャの話題を Python に特化した内容で解説しているのがよいと私は思います。プログラミング一般でもてはやされるアーキテクチャがその言語で必ずしもよい選択肢とは限りません。他言語のプラクティスをそのまま導入してもうまくいかないこともあるでしょう。

私はプログラミング言語の特性や、これまでの経緯を踏まえてそのプログラミング言語にあったアーキテクチャを採用するのがよいと最近よく思うようになりました。世の中の流行りを追いかけるよりも、その言語で培われた実績の積み重ねの方が優れている場合があるからです。

本章では歴史的な経緯も踏まえ、次の順番で説明されています。

マルチスレッディングの背景や制限を説明した後に、マルチプロセッシングや asyncio の話題に移っていきます。

asyncio が導入された背景として、それまで存在していた Python の非同期フレームワークやライブラリにおいて、各々における互換性や相互運用性を確保する標準的なイベントループインターフェースを提供することが狙いであったと解説されています。したがって、asyncio はアプリケーション開発者が直接使うというよりも、どちらかと言えば、フレームワーク/ライブラリの開発者が利用するモジュールのようにみえます。

そうとはいえ、asyncio とともに導入された asyncawait のキーワードや ネイティブコルーチン の概念など、アプリケーション開発者にとっても学ぶべきパラダイムはあります。

最後は ZeroMQ というメッセージキューを使って、マルチプロセス間での通信を行うサンプルコードも紹介しています。非同期/並行処理は、人間にとって難しく、なるべく言語機能やフレームワーク/ライブラリの機能を使いつつ目的の処理を実装するのが品質の高いアプリケーションを開発する戦略と言えるでしょう。

第13章 コーディングを減らしてコードを増やす

なんとなくおもしろい章のタイトルですよね。コーディング減らせばコードは減るはずなのに増やそうというわけです。これはどういうことでしょうか?

ある機能を汎用的に使えるようにしたり、ボイラープレート (繰り返し記述するようなものを指す) コードを減らしたりして、応用コードではそれらを再利用することで全体のコーディング量は減らしつつ、応用コードを増やすということを意図したタイトルなのでしょうかね?この章では次の内容を解説しています。

  1. sixを使ってPython 2とPython 3をサポートする
  2. PythonLispのように使ってシングルディスパッチャを作成する
  3. コンテキストマネージャ
  4. attrsを使って決まりきったコードを減らす

これからは Python 2 の話は忘れてよいので six を使う場面はどんどんなくなっていくでしょう。コンテキストマネージャももう十分によく使われていますし、私もよくユーザー定義のコンテキストマネージャーを実装します。また監訳注にも補足がありますが、Python 3.7 で導入された dataclass は attrs からインスパイアされたものです。

masahito.hatenablog.com

消去法というわけでもありませんが、Python 3.4 で導入された ジェネリック関数 を定義するシングルディスパッチについて紹介してみます。導入当初は記事をよくみかけましたが、それから時間が経って最近はあまりみかけない気もします。

第9章でも述べられていたように、著者は Lisp 愛好家のようなので CLOS (Common Lisp Object System) と同様の方法でジェネリック関数をディスパッチする方法を定義しているシングルディスパッチの仕組みを Lisp の考え方と共に Lisp コードも交えて説明しています。Lispジェネリックな関数をどう扱うのかの雰囲気もわかっておもしろいです。

シングルディスパッチと関数のオーバーロード

シングルディスパッチができて何が嬉しいかを私なりに考えてみましたが、簡潔に言語化することは私には難しかったです。そこで考察したことをそのまま書いてみます。

この節では functools.singledispatch を扱う次のようなサンプルコードが紹介されています。これは実践的に意味のない、ただのサンプルコードでしかないのであまりよい例とは言えませんが、比較しやすいようにこのサンプルコードをベースに説明を続けます。

次の場所にここで説明するサンプルコード一式を置いてあります。

import functools

class SnareDrum: pass
class Cymbal: pass
class Stick: pass
class Brushes: pass

@functools.singledispatch
def play(instrument, accessory):
    raise NotImplementedError('Cannot play these')

@play.register(SnareDrum)
def _(instrument, accessory):
    if isinstance(accessory, Stick):
        return 'POC!'
    if isinstance(accessory, Brushes):
        return 'SHHHH!'
    raise NotImplementedError('Cannot play these')

@play.register(Cymbal)
def _(instrument, accessory):
    if isinstance(accessory, Brushes):
        return 'FRCCCHHT!'
    raise NotImplementedError('Cannot play these')

print(play(SnareDrum(), Stick()))
print(play(SnareDrum(), Brushes()))
print(play(Cymbal(), Stick()))
print(play(SnareDrum, Cymbal()))

play(instrument, accessory) という関数の第一引数 instrument によって呼び出す関数をディスパッチするようなサンプルコードです。このサンプルコードを別のアプローチで実装すると次のようなコードになります。関数型プログラミングオブジェクト指向プログラミングで実装の違いはあれど、どちらも同じ機能は提供できます。どちらがよいという話ではありません。

class Instrument:
    def play(self, accessory):
        raise NotImplementedError('Cannot play these')

class SnareDrum(Instrument):
    def play(self, accessory):
        if isinstance(accessory, Stick):
            return 'POC!'
        if isinstance(accessory, Brushes):
            return 'SHHHH!'
        raise NotImplementedError('Cannot play these')
...

def play(instrument, accessory):
    return instrument.play(accessory)

print(play(SnareDrum(), Stick()))
print(play(SnareDrum(), Brushes()))

どちらのコードでも play(instrument, accessory) という同じ関数名を異なるインスタンスからも使えることに意義があります。ここではこのことをジェネリック関数と呼んでいます。後者のコードはポリモルフィズムと呼ばれたりします。

ここでそれぞれのインスタンスが受け取る引数が異なるパターンも考えてみます。説明をわかりやすくする意図で引数の数が異なる場合を考えます。

@play.register(Cymbal)
def _(instrument, accessory, other):
    if isinstance(accessory, Brushes) and isinstance(other, Stick):
        return 'FRCCCHHT!'
    raise NotImplementedError('Cannot play these')

...

print(play(Cymbal(), Brushes(), Stick()))

ジェネリック関数の定義では instrumentaccessory の2つの引数しか受け取っていませんが、 Cymbal に紐づく関数でさらに other という3番目の引数を受け取るように変更してみます。結論から言うと、SnareDrum に紐づく関数では2つの引数を、 Cymbal に紐づく関数では3つの引数を受け取って意図したように動きます。

@functools.singledispatch の公式ドキュメントには型の違いによる、関数のオーバーロード の説明がされているので受け取る引数の数が異なっても動くのはただの実装依存かもしれませんが、ここでは関数のオーバーロードの説明をわかりやすくする意図で引数の数が異なるケースで続けます。

例えば、次のようなコードを考えます。

def add(x, y):
    print(f'{x=} + {y=} = {x + y}')

def add(x, y, z):
    print(f'{x=} + {y=} + {z=} = {x + y + z}')

add(1, 2, 3)
add(1, 2)  # この呼び出しはエラーになる

add() という同じ関数名で異なる引数を受け取る関数を定義したい場合、Python では関数のオーバーロードをサポートしないため、このコードでは後優先で3つの引数を受け取る後者の関数しか使えません。Python に限らず、動的型付け (ダックタイピング) の言語では実行時に型チェックすることから、関数のオーバーロードができるメリットよりも、呼び出し側が引数を間違えてしまったり、引数違いで同じ関数の実装が複数あることによる、コードの見通しの悪さといったデメリットの方が上回るからでしょう。

関数のオーバーロードがなくても、例えば、デフォルト引数を使うことでその目的は果たせます。これは引数が2つでも3つでも意図したように振る舞います。

def add(x, y, z=0):
    print(f'{x=} + {y=} + {z=} = {x + y + z}')

add(1, 2, 3)
add(1, 2)

しかし、デフォルト引数だと、受け取る引数のパターンが増えるとややこしくなるかもしれません。やっぱり関数のオーバーロードっぽいことがしたくなったとしても、例えば、次のようなデコレーターを使って簡単に実装することもできます。

import inspect

class Overload:

    def __init__(self):
        self.namespace = {}

    def __call__(self, func):
        spec = inspect.getfullargspec(func)
        self.namespace[len(spec.args)] = func
        def wrapped(*args):
            f = self.namespace.get(len(args))
            if f is None:
                raise NotImplementedError(f'Not defined for {len(args)}')
            return f(*args)
        return wrapped

overload = Overload()

@overload
def add(x, y):
    print(f'{x=} + {y=} = {x + y}')

@overload
def add(x, y, z):
    print(f'{x=} + {y=} + {z=} = {x + y + z}')

@overload
def add(a, b, c, d, e, f, g):
    print(f'{a + b + c + d + e + f + g}')

このサンプルコードは引数の数だけに着目した関数のオーバーロードのデコーレーター実装ですが、これを汎用的にしたものが @functools.singledispatch という見方もできます。もちろん第1引数の型を使ってディスパッチする機能も使えるわけですが。

シングルディスパッチかポリモルフィズムかという議論は、複数のプログラミングの話題の背景を含んでいるように私は思います。

私は型システムに明るくないので適切な言い方ではないかもしれませんが、プログラマーの設計の考え方によってシングルディスパッチを好むスタイルがあることも理解できます。

余談ですが、私も過去に技術考察の一環で enum の定数固有メソッドを考察したときにオーバーロードの仕組みを実装してみたこともあります。用途によっては、関数のオーバーロードにより、コードの見通しがよくなるケースもあるのではないかと個人的には思います。

qiita.com

シングルディスパッチについての、私の結論としては、コーディングスタイルや設計の好みで使い分ければいいのではないでしょうか。

まとめ

私の興味・関心をもとに他の Python の書籍ではあまり出てこないような話題を取り上げてみました。

著者が Lisp 愛好家なので関数型プログラミングのプラクティスや話題が全体を通して出てくるところも特徴的かもしれません。本稿では紹介しませんでしたが、第8章は「関数型プログラミング」というタイトルですし、上述したように第9章では ast モジュールや Hy を取り上げていました。

Python は手続き型のプログラミング言語ではあるものの、関数型プログラミングの機能 (ライブラリ) もいくつか備えています。関数型プログラミングの考え方やアプローチを学ぶ上でも本書はよい入門になるように思います。

2020-06-13 追記

勉強会の LT で第8章「関数型プログラミング」の一部を紹介しました。

docs.google.com

2020-11-21 追記

勉強会の LT で第9章「AST、HYLispライクな属性」の一部を扱いました。

docs.google.com

できる 仕事がはかどるPython自動処理 全部入り。の第4刷が決定しました

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

t2y.hatenablog.jp

おかげさまでまだ売れ行きは好調なようです。本書を購入していただいた方々、ありがとうございます。

このたび、第4刷の重版が決定しました。

【第4刷】3月16日(月)重版出来

book.impress.co.jp

Chapter 10 「065 Web ページを Selenium で操作する」の修正について

第4刷で本節の一部のコードが修正されます。 Selenium という Web ブラウザを操作するライブラリの使い方を紹介している節です。

これまでは Yahoo! JAPAN のトップページを題材として Selenium の操作を説明していました。

PC 版 Yahoo! JAPAN のトップページを 2019 年 10 月 1 日に刷新、 ... (中略) ... Yahoo! JAPAN トップページは 2008 年 1 月 1 日に大規模なリニューアルを行いました。その頃からある程度の改修はあったものの、基本的にはコードの継ぎ足しで修正を加えている状態でした。

techblog.yahoo.co.jp

なんと11年間変わらなかったトップページが2019年10月1日に大きな改善が入り、トップページの HTML の構造が変わってしまい、本書で説明している方法では動作しなくなってしまいました。

そこで第4刷では次のように Wikipedia のトップページに対して同じ操作を行うように変更しています。 申し訳ありませんが、第1-3刷を購入された方は次の内容に読み替えて Selenium の操作を確認してください。

変更した内容

まず、Wikipediaトップページの URLを指定してブラウザに表示させます。get()というメソッドを使います。

>>> driver.get("https://ja.wikipedia.org/")

f:id:t2y-1979:20200229144421p:plain

検索語として「Python」と入力します。検索ボックスには searchInput というid属性が付与されているのでCSSセレクターで指定して、send_keys()でキー入力を送信します。

>>> driver.find_element_by_css_selector("#searchInput").send_keys("Python")

f:id:t2y-1979:20200229144504p:plain

検索候補ウィンドウの検索ボタンにはid属性 searchButton が付与されているので、同様にCSSセレクターで指定して、click()でクリックします。

>>> driver.find_element_by_css_selector("#searchButton").click()

続けて、ブラウザの「戻る」を行ってWikipediaのトップページに戻ったあとに、"ログイン"というリンクを開いてみましょう。

>>> driver.back()
>>> driver.find_element_by_link_text("ログイン").click()

ビジネスPythonを学ぶ会の活動と slack の紹介

本書の読者サポートも兼ねて私が神戸で運営しているコミュニティがあります。 このコミュニティは難しいことを扱うわけではなく、またすぐに業務で役に立たないかもしれませんが、コミュニティとして長く続けられることだけを目標にしています。Python で困ったときに気軽に相談できる場になればいいなと思います。

bizpy.connpass.com

本書の対象読者と同様、Python 入門を終えたばかり初学者、さらに IT エンジニアではない次の方々を対象としています。

いまのところ、月1回の勉強会を開くことを予定しています。地理的に参加できない方もいるでしょうからオンラインでのコミュニケーションが取れるように bizpy.slack.com を開設しています。興味のある方はこちらの 招待リンク からご参加ください。

いまは Excel ファイルを操作する openpyxl ライブラリの操作について取り上げています。そうすると、プログラマーではないけれど、企業でデータ分析の実務を行っている方々が参加してくれます。参加者の実際の実務で行っている内容をみんなで共有したり、参加者が書いたコードをもとにレビューしたり、私が開発やデバッグに役立つ手法を紹介したりといった、そういうゆるい感じの勉強会をしています。

今後も勉強会や読者サポートの過程でお伺いした内容や気づきを留めていき、いつか本書を改訂できる機会ができたときに活かせるように努めていきたいと思います。

まとめ

本書の出版を契機にして、これまで私が接点のなかった方々と Python プログラミングを通してお話する機会が増えました。

私自身、プログラマーとして10年以上働いてきて、ちょうどいまキャリアの棚卸しをしている時期です。今後は培ってきたプログラミングのスキルを活かして世の中の役に立つプロダクトやビジネスを創造することにも挑戦していきたいと考えています。ビジネスパーソンの方々が普段行っている業務や課題意識を知る機会があることは、私にとっては新鮮で興味深いものです。

またプログラミングできる人が増えること自体もいまの世の中にとっては良いことだろうと私は考えています。

リファレンス

t2y.hatenablog.jp

できる 仕事がはかどるPython自動処理 全部入り。の第3刷が決定しました

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

t2y.hatenablog.jp

おかげさまで書店ならびにオンライン書店でも好調な売れ行きであるそうです。本書を購入していただいた方々、ありがとうございます。

このたび、第3刷の重版が決定しました。

【第3刷】9月2日(月)重版出来

book.impress.co.jp

オンライン書店による不評から始まった読者サポート

本書は発売当初からリアル書店では好調な売れ行きであると聞いていたものの、オンライン書店では微妙な状況でした。それはおそらく INTERNET Watch での書籍紹介の記事が変なバズり方をしたせいだと私は考えています。

internet.watch.impress.co.jp

本書の対象読者は Python 入門を終えたばかり初学者、さらに IT エンジニアではない次の方々を対象としています。

書名から誤解を招きやすい点や RPA の気運が高まりつつある背景の中、INTERNET Watch の記事をみて (中身を確認せずに) IT エンジニアの方々もオンライン書店で購入された方が多かったのではないかと推測します。

Amazon のカスタマーレビューを時系列にみていただければわかりますが、当初は本書の内容にがっかりしたレビューが多かったです。著者からすると、意図した対象読者へ届いていないことを歯がゆく思ったこともありました。とはいえ、経緯がどうであっても出版してしまったものはしょうがないのでできる範囲でサポートをしようと、身近なところで読者サポートのための勉強会を隔週で開催してきました。

bizpy.connpass.com

回を重ねるごとに勉強会のメンバーも少しずつ増えてきたこともあり、「ビジネスPythonを学ぶ会 (bizpy)」の slack も準備しているところです。いまのところ、読者サポートの勉強会は関西でしか開催できません。もしオンラインでもよいので本書の内容についてやり取りしたいという方は connpass の「ビジネスPythonを学ぶ会」のグループメンバーに登録しておいてください。後日、こちらの準備が整い次第、bizpy slack への招待リンクをグループメンバーへ送る予定です。

さらに Python イベントにも登壇して本書はビジネスパーソンやノンプログラマー向けの、Python を入門を終えた方々を対象としていると訴えることにしました。そのときの登壇資料が次になります。

www.slideshare.net medium.com

どんな理由であれ、本書を手に取っていただける読者が増えることは著者にとって嬉しいことではありますが、必要のない人にまで届けようとは思いません。今後も本書はビジネスパーソンやノンプログラマー向けの書籍であることを訴求していこうと思います。

そういった活動の成果が出てきたのか、2019-07-13 時点では 2.7 だったカスタマーレビューも本日時点で 3.1 まで上がりました。ここ最近は本書の対象読者である方々によるレビューが続いているおかげだと考えています。

f:id:t2y-1979:20190812134943p:plain
2019-08-12 時点での Amazon カスタマーレビュー

またレビューの良し悪しに関わらず、レビューを投稿していただいた読者の方々には感謝を申し上げます。よいレビューは著者にとって励みになりますし、わるいレビューは気づきを得られるのでどちらも次の行動への取っ掛かりになります。

例えば、わるいレビューから得た気づきで書いた補足記事が次になります。残念ながら重版となる第3刷ではこの補足記事を追加することはできませんでした。しかし、この補足記事へのリンクを「ここもポイント」というコラム形式で「Chapter 8 テキストデータの処理」の最後に追記しました。

t2y.hatenablog.jp

今後も勉強会や読者サポートの過程でお伺いした内容や気づきを留めていき、いつか本書を改訂できる機会ができたときに活かせるように努めていきたいと思います。

重版情報をスクレイピングしてみよう

第3刷の発売が決定しましたという案内だけではおもしろくないので本書の内容でできることの宣伝も少し書いてみます。丁寧な説明は意図的に省きますのでもう少し詳しい説明を読みたくなったら本書の購入を検討してください。

重版情報はインプレスさんのスタッフブログに公開されていますが、どうもこのサイトは RSS が提供されていないようです。

book.impress.co.jp

重版情報をチェックしたい方がいるかどうかわかりませんが、こういったときは Web スクレイピングすることでほしい情報を取り出すことができます。本書の「062 Web ページから要素を取り出す」のサンプルコードを応用して挑戦してみます。また本書の「061 Web スクレイピング」の節にある注意事項も書いておきます。スクレイピングに慣れていない方は、スクレイピングを行うときのマナー、約束事項として次のことを必ず守るようにしてください。

  1. 取得したデータは個人的に参照するのみに留める
  2. スクレイピング禁止となっていないかWebサイトの規約を確認する
  3. 大量のリクエストを送らない

本稿で紹介するスクレイピングのサンプルコードは次の URL からダウンロードできます。

CSS セレクターを取得する

Google Chrome (以下Chrome) に限った話ではありませんが、最近の Web ブラウザには開発者のデバッグを助けるためのツールが標準でインストールされていてすぐに使える状態になっています。本書では Chrome を使って説明しているので同様に Chrome を使います。インプレスさんのスタッフブログを Chrome の開発者ツールを使って HTML の要素を表示させたものが次になります。

f:id:t2y-1979:20190812151227p:plain
Google Chrome の開発者ツールを使って調べる

h2 や div といった HTML タグの構造や CSS の class としてどのようなものが設定されているかがすぐにわかります。

本のタイトルと重版についての情報を書いているところが離れているのでそれらを含む上位の div.block-book-detail までの CSS セレクターを Chrome 機能で取得します。

body > div.block-wrap > div.block-content > main > div > div:nth-child(3) > div.block-book-detail-box-body.module-usr-text > div.block-book-detail

Beautiful Soup の Tag オブジェクトを使う

HTML の要素を取り出すために本書では Beautiful Soup というツールを使います。Beautiful Soup で CSS セレクターを用いると Tag オブジェクトが返されます。先ほどの CSS セレクターで取得した Tag オブジェクトが detail にセットされます。そこからさらに CSS セレクターを指定して必要な要素のみを取り出します。

    hrefs = detail.select('h2 > a')
    reprint_messages = detail.select('div.module-book-copy > b')

このとき hrefsreprint_messages は次のようなリストが返されます。

ipdb> pp hrefs
[<a href="https://book.impress.co.jp/books/1118101147" title="できる 仕事がはかどるPython自動処理 全部入り。">◆ できる 仕事がはかどるPython自動処理 全部入り。</a>,
 <a href="https://book.impress.co.jp/books/1119600001" title="ねじ子のヒミツ手技#">◆ ねじ子のヒミツ手技#</a>,
 <a href="https://book.impress.co.jp/books/1119600002" title="ねじ子とパン太郎のモニター心電図 改訂版">◆ ねじ子とパン太郎のモニター心電図 改訂版</a>]
ipdb> pp reprint_messages
[<b class="module-book-price" style=" font-size: 120%; border: 0px; ">【第3刷】92日(月)重版出来</b>,
 <b class="module-book-price" style=" font-size: 120%; border: 0px; ">【第4刷】92日(月)重版出来</b>,
 <b class="module-book-price" style=" font-size: 120%; border: 0px; ">【第2刷】92日(月)重版出来</b>]

個別に要素のリストを取得できました。これらの情報を一緒に扱いたいとき、方法はいくつかありますが、zip() という組み込み関数で組み合わせるのがシンプルに表現できます。

ipdb> pp list(zip(hrefs, reprint_messages))
[(<a href="https://book.impress.co.jp/books/1118101147" title="できる 仕事がはかどるPython自動処理 全部入り。">◆ できる 仕事がはかどるPython自動処理 全部入り。</a>,
  <b class="module-book-price" style=" font-size: 120%; border: 0px; ">【第3刷】92日(月)重版出来</b>),
 (<a href="https://book.impress.co.jp/books/1119600001" title="ねじ子のヒミツ手技#">◆ ねじ子のヒミツ手技#</a>,
  <b class="module-book-price" style=" font-size: 120%; border: 0px; ">【第4刷】92日(月)重版出来</b>),
 (<a href="https://book.impress.co.jp/books/1119600002" title="ねじ子とパン太郎のモニター心電図 改訂版">◆ ねじ子とパン太郎のモニター心電図 改訂版</a>,
  <b class="module-book-price" style=" font-size: 120%; border: 0px; ">【第2刷】92日(月)重版出来</b>)]

あとは出力するときに Tag オブジェクトから必要な属性を指定するだけです。

最終的なサンプルコードは次の通りです。

import requests
from bs4 import BeautifulSoup

page_data = requests.get('https://book.impress.co.jp/staff_blog/').text
page = BeautifulSoup(page_data, 'lxml')
details = page.select("""
body > div.block-wrap > div.block-content > main > div > div:nth-child(3) > div.block-book-detail-box-body.module-usr-text > div.block-book-detail
""")

for detail in details:
    hrefs = detail.select('h2 > a')
    reprint_messages = detail.select('div.module-book-copy > b')
    hrefs_and_reprints = zip(hrefs, reprint_messages)
    for href, reprint in hrefs_and_reprints:
        print(f'{href["title"]}: {reprint.text}')

実行結果は次の通りです。

$ python impress_book_reprint_list_all.py
できる 仕事がはかどるPython自動処理 全部入り。: 【第3刷】9月2日(月)重版出来
ねじ子のヒミツ手技#: 【第4刷】9月2日(月)重版出来
ねじ子とパン太郎のモニター心電図 改訂版: 【第2刷】9月2日(月)重版出来

私が興味があるのは「できるPy」のみなので、さらに if 文で条件を限定することでほしい情報のみを絞り込めます。

    for href, reprint in hrefs_and_reprints:
        if href["title"] == 'できる 仕事がはかどるPython自動処理 全部入り。':
            print(f'{href["title"]}: {reprint.text}')

実行結果は次のようになります。

$ python impress_book_reprint_list_dekiru.py
できる 仕事がはかどるPython自動処理 全部入り。: 【第3刷】9月2日(月)重版出来

まとめ

本書の「062 Web ページから要素を取り出す」のサンプルコードを応用して RSS 情報のないサイトのページを取り出してみました。

本書で扱う「自動化」というのはこの程度の内容を十数行から数十行のサンプルコードで解説します。このぐらいのレベル感で目次にある内容の、Python プログラミングやツール・ライブラリの使い方を知りたいという方にとっては本書は役に立つでしょう。もしくは、このぐらいの内容なら自分で調べてすぐに実装できるという方であれば本書は不要になります。

どちらとも言えない方はぜひリアル書店へ足を運び、そこで中身を少し読んでみて、その直感で選択していただければと思います。

リファレンス

少し古いバージョンですが、Beautiful Soup ドキュメントの日本語訳もありました。

kondou.com

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

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

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自動処理 全部入り。 (「できる全部入り。」シリーズ)

Python と型ヒント (Type Hints) と #pyconjp

先週末は PyCon JP 2015 に参加してきました。

どうでも良いことですが、たまたま会社がシルバースポンサーになっていましたが、参加そのものは個人でした。結果的には会場に会社ブースもあったため、そこでお手伝いもしつつの参加となりました。私以外にはどうでも良いことですね。

会社のブログにもイベント参加の所感を書いたので興味があればそちらもどうぞ。

型ヒントの発表

年明けから余裕があったので型ヒントの調査をしてきました。私自身、難しいことは分からないのですが、型システムに興味が出てきたところでいろんな言語の型システムをみてコードを書いたり、その特徴を調べたりするのがいまはおもしろいです。最近は Go 言語を主に書いていて型推論を伴う静的型付け言語の強力さを実感したりしています。

そして動的型付け言語に型ヒントを入れるという逆のアプローチもやはり興味深いです。Python では mypy が成功を収め、その型アノテーションの構文が PEP 484 で標準化されました。これまでも何度か mypy についての記事を書いてきました。細かいところは変わってしまっていますが、経緯や背景は変わっていないので興味のある方は以下も参考にしてください。

そして、これまで私が調べたことの集大成として PyCon JP 2015 で発表してきた次第です。

togetter はこちら。

聴講してくれたのは100人前後だと思うのですが、発表者と聴いている人たちとの距離感が近かったので私の中では発表しやすかったように思います。

発表直前、小山さん (@) が私の真前のスペースで

ここ地べたに座って聴いても良いっすか?

と声を掛けてくれて適当に「良いですよー」と答えながら、なんか少し緊張がほぐれて発表を開始しました。

何度か聴衆席に質問を投げかけながら進めました。Pythonのパラドックス を知らない人が大半だったりと世代が変わっているなぁと実感しました。発表の中で Python3 のパラドックスみたいな冗談も言っていますが、意図としては Python3 への移行を阻むものはほとんどないはずなので新しいものを作るときは Python3 を使おうよという呼びかけです。

移行が着実に進んでいるというの以下のアンケートなどから伺えます。

型ヒントについての詳細は PEP 484 型ヒントの翻訳 を眺めてもらうのが一番良いと思います。それなりの分量がありますが、興味のあるところから読み進めるのでも良いでしょう。サンプルコードも付いているので分かりやすいです。

型チェッカー

本節では発表のときにあまり触れなかった、型チェッカーとしての mypy を使ってみた私の所感を書きます。PEP 484 では型アノテーションの構文の標準化のみで型チェッカーは付属していません。そのため、依然として mypy は自分でインストールしなければなりません。

結論から先に書くと、型チェッカーとしての mypy はまだ実用レベルとは言えないです。github の mypy プロジェクトをみても250以上の issue が報告されており、期待する動作には至っていないようにみえます。

ちなみに mypy の PyPI 上のパッケージ名は mypy-lang というパッケージ名です。mypy という名前のパッケージが既に使われているためです。ちなみにこれは wsgi フレームワークだそうです。インストールするときには間違えないようにご注意を。

mypy の最新バージョンは 2015-04-05 に 0.2 というバージョンがリリースされています。この時点では PEP 484 のドラフト状態であり、そのときに決まっていた内容、おそらくは PyCon US 2015 で一定の合意を得た内容だったのではないかと推測します。

その後、PEP 484 が正式に認可されたのが 2015-05-22 です。

なぜか7月前後の master ブランチの開発は停滞していますが、その後8月頃からまた開発がアクティブになっています。それでも Python 3.5 と同じタイミングで 0.3 をリリースできてなく、また雰囲気的にも近々リリースするようにみえません。

github から mypy のソースをクローンしてきて、発表前にいくつかサンプルコードの型チェックなどを試したりしてみました。そのときにちょっと触って気付いたこと = うまく動かないところ = 既知の issue をいくつか見つけました。

やっぱり型チェッカーって大変なんだなぁというのが素朴な感想です。issue のページで label:pep484 でフィルターしても現時点で17個あります。mypy の 0.3 がリリースされるまでは型チェッカーは様子見といったところかもしれません。

今朝 git pull して動かそうとしたら bultins が見つからないというエラーになりました。

(mypy)$ mypy tutorial.py 
tutorial.py:1: error: Could not find builtins
...

パスの問題のようです。

いま mypy にコントリビュートするチャンスがたくさんありそうです。

型チェックは必要?

発表を聴いてくれていた同僚のデータサイエンティストに発表後どんな印象を受けましたか?と尋ねてみました。すると、やはり難しいと答えが返ってきました。ジェネリクスとか、これは Python なの?といった印象を受けたそうです。ジェネリクスの話をする際、会場で Java やったことがある人?と尋ねたら8割以上、手が挙がったので「ジェネリクスとは」みたいな話を省いてどんどん話を進めたのもあるかもしれません。

発表後の質疑応答においても、例えば 共変性と反変性 とか、Python はそういうことを考えずに簡単に使えて良いのに、、、といった質問もありました。私も説明が難しいからこの内容は発表から省いたのですが、確かに型ヒントをちゃんと書こうと思ったら分かっている人しか書けない、直感的に書ける類のものではないという意味で難しいです。

型チェックを誰が必要としているか?というと、その回答は学習コストやメンテナンスコストを払ってでも実行前に型チェックしたいかどうかの動機次第かなと思います。以下の清水川さん (@) の記事で Guido 自身も PyCharm や Google でも似たようなスタブファイルを作っていたから標準化することに意義があるんだと語っています。

私の経験からだと、実行前にエラーが知りたいケースは高い SLA が要求されるアプリ、または大規模なアプリだと思います。チーム開発で日々コードを書いてコミットしていると、他の人が何をやっているか分からないために認識の違いによるバグは常に入ってしまいます。

そういったバグを見つけるために動的型付け言語はテストをたくさん書くことで一定の品質を担保してきたわけですが (もちろん静的型付け言語でもテストは必要ですが)、その規模拡大に伴ってもうしんどくなってきたというのが現状ではないかと私は思います。アプリの規模が大きくなればなるほど、テストで品質を担保するのが難しくなります。

  • 全てのテストケースを網羅できない
  • テストのメンテナンスコストがかかる
  • テストの質が開発者のスキルに依存する

型チェックはテストなのか?と言うと議論はありそうですが、型レベルの操作において不整合がないことは、人間がプログラムを書く上で失敗しやすいミスを防いでくれます。

  • 人間はコードを書き間違える (typo)
  • 人間は全てのソースコードを把握してコードを書けない
  • 人間は時間が経つとそのソースコードを書いたときの経緯を忘れる

これらの失敗に対して型チェックは有効です。自分が全てを把握できていないコードベースにコードを追加していくとき、エラーを未然に防ぐことへの安心感をプログラマーは求めている気がします。

まとめ

型ヒントを扱う型チェッカーや IDE がどのぐらい普及するか、またはさらに便利な用法が出てくるか、まだまだこの先どうなるかは分かりません。とは言え、型ヒントそのものはあくまでオプションの位置付けなので悪い作用をもたらすことは何もないと思います。私ももう少し mypy が安定してきたら、それを使うテストツールなどを作ってみようと考えています。