依存モジュールが少ないソースコードを読む - スペシャリスト岡野真也に聞くPythonの読み方と使い方
現在、多くの支持を集めるPython。10年以上にわたりPythonを使い続ける岡野真也さんに、同言語の学び方、使い方を聞きました。
機械学習やデータサイエンスの隆盛に伴い、Pythonは多くの人に使われる言語になりました。GitHubの「The State of the Octoverse 2019」のレポートによると、GitHubリポジトリのコントリビューターから人気のあったプログラミング言語として、PythonはJavaScriptに次いで2位となっています。
「さまざまな課題を、楽に解決できるのがPythonの魅力」と語るのは、10年以上も前からPython・Djangoフレームワークのヘビーユーザーであり続けてきた岡野真也(おかの・しんや/ @tokibito )さん。彼はいかにして、設計や実装の技術を磨いてきたのでしょうか。そして、言語の特性を最大限に引き出すノウハウとは。より良いPythonの書き手になるために必要な知恵を伺いました。
- Pythonは学習コストが低く、可読性も高い言語
- Pythonの設計を学ぶには、ライブラリのコードを読むべし
- パフォーマンス課題を解決するために採用すべきアプローチ
- 影響の大きかった、2系から3系への非互換変更
- 文法を複雑にせず、シンプルであれ
- Pythonならば、さまざまな課題をより楽に解決できる
Pythonは学習コストが低く、可読性も高い言語
——岡野さんは何をきっかけに、Pythonを使い始めたのですか?
岡野 Pythonを使い始めたのは2006年で、当時の私は大学生でした。大学の研究室でPython製のCMSであるPloneを用いており、Ploneの挙動をカスタマイズするにはPythonを書く必要がありました。見よう見まねでPythonを書くところからのスタートです。
より本格的にPythonを書くようになったのは、2007年からですね。その年に、Djangoフレームワークを触り始めました。個人のブログを立ち上げようと思ったのですが、Ploneは個人で使うには複雑で学習コストも高い。「他に、Python製の良いフレームワークはないだろうか」と探していたら、偶然「Djangoフレームワークで簡単なブログをつくってみた」という趣旨の記事を見かけて、これがDjangoとの出会いになりました。
——Pythonの利点としては、どのような点が挙げられますか?
岡野 Pythonは文法がシンプルで、予約語の種類が少ないです。学習コストが低く、他の人の書いたコードも読みやすい。コーディングでは書いている時間よりも読んでいる時間の方が圧倒的に多くなるものです。「読みやすい」とは言語として重要な特性だと思うのです。それからPythonには多種多様なモジュールが用意されているため、何か実現したいことがある場合に、それらのモジュールを活用できるケースが多いです。
——なるほど。Djangoフレームワークに関してはいかがでしょうか。
岡野 Djangoフレームワークの初期に感じた利点は、生成されるファイルの種類が圧倒的に少ないことです。
他のWebフレームワークの場合、プロジェクトを自動生成した際に大量のファイルが作成されるものもあります。一方でDjangoフレームワークは、私が使い始めた頃のバージョンではプロジェクト新規作成時に3~4個くらいのファイルしか生成されませんでした。
当時の私はプログラミングスキルがそれほど高くありませんでしたが、ファイル数が最低限であることで「どこに何を記述すればいいのか」が明確になり、簡単に実装を進められました。
さらに、Djangoフレームワークには組み込みの管理画面がついていますが、この機能は非常によくできています。Modelクラスと少しの設定を書けば、それだけで各テーブルのデータを参照・編集できる管理画面が使えるようになります。そのままプロダクションでも使えるくらいのクオリティーですし、カスタマイズも容易です。
——その機能を用いることで、開発やテスト、運用が非常に楽になりそうですね。
岡野 他には、Djangoフレームワーク内蔵のアプリケーションと呼ばれる仕組みが便利です。これは、Pythonモジュールの形でアプリケーションをインストールして、設定ファイルによって特定のアプリケーションを有効にすることで、さまざまな機能を導入できるというものです。
INSTALLED_APPS = [ # ... 'some_app_name.apps.SomeAppConfig', 'debug_toolbar' ]
——読者の方におすすめの、開発に役立つアプリケーションはありますか?
岡野 Django Debug Toolbarが便利です。これは、開発中にWeb画面の右側にデバッグ用の情報(リクエスト・レスポンスの詳細や実行されたSQL、セッションの内容など)を表示できる機能です。
多種多様なアプリケーションを利用して開発の利便性を向上させられるという意味でも、Djangoフレームワークは非常に優れています。
Pythonの設計を学ぶには、ライブラリのコードを読むべし
——Pythonの言語特性を学ぶためには、何を学習をするといいでしょうか?
岡野 おすすめは、さまざまなライブラリのソースコードを読むことです。先ほど、Pythonのコードは読みやすいという話をしましたが、コード量が少ないものであれば、サードパーティのライブラリも読むのはそれほど難しくはありません。私もPythonを学びたての頃は、よくDjangoフレームワークのソースコードに目を通していました。
——Djangoフレームワーク以外には、どのようなライブラリやフレームワークのソースコードが読みやすいですか?
岡野 HTTP通信ライブラリであるRequestsモジュールや、Webアプリケーションのリクエストとレスポンスをオブジェクト化するためのWebOb、また、Pythonに内包されている各種標準モジュールもおすすめです。選定基準としては「依存しているモジュールの種類が少ないこと」を軸にするといいと思います。サードパーティ製のモジュールに依存しておらず、Pythonの標準モジュールだけに依存しているようなものが参考になると思います。
——コードリーディングにおいて、初心者はどのような点に着目して読むと、より効果的でしょうか?
岡野 クラスやモジュール、関数などが「どのような単位で分けられているか?」を見ていくといいです。広く使われるライブラリのソースコードを読み解いていけば「この単位でモジュール分割することで、メンテナンスがしやすくなる」という感覚がつかめます。
——岡野さん自身は、Djangoフレームワークのソースコードを読んで、どのようなことを学ばれましたか?
岡野 例えば、オブジェクト指向プログラミングでは基本的なことなのですが、「クラス内の'__init__'
メソッド(初期化の処理)に初期化以外の意味のある処理を含めない」などでしょうか。初期化の処理はクラスを使用するとき必ず呼ばれますから、クラスのオブジェクトを準備する以外の処理を書いてしまうと、影響範囲が非常に大きくなってしまい、テストもしづらくなります。何か処理を実行したいならば、別のメソッドを用意し、そのなかで行うべきだと学びました。
他には、先ほどの話にも出てきましたが、Djangoフレームワークのアプリケーションにおけるpluggableの設計からも多くの学びを得ました。Djangoフレームワークでは各機能ごとに抽象化されたクラスが用意されていて、これらを継承したクラスを利用することで、各機能のふるまいを変えることができます。Django以外のWebアプリケーションフレームワークでもよくある設計ですが、これは自分でライブラリやモジュールを作成する際に、かなり参考になりました。
また、Djangoフレームワークでは、設定ファイルがPythonのモジュールになっていて、例えば有効にするアプリケーションの情報は文字列のリストで設定します。すると、その名前を持ったモジュールがあるかどうかを探索して読み込んでくれる。つまり、設定ファイル内では直接的にモジュールのインポートを行っていません。
この設計を用いると、設定ファイルであるPythonモジュールがアプリケーションコードへ依存し、循環参照になってしまう問題を回避できます。これにより、循環参照が発生しにくいプラグインのような仕組みを実現できているのも、興味深いと感じました。
パフォーマンス課題を解決するために採用すべきアプローチ
——Pythonは「処理速度が遅い」という意見が持たれることもあります。
岡野 確かに、Pythonはソースコードをコンパイルしてバイトコードに変換してからVMが実行するという処理の流れになっているため、ある程度遅くなるのは言語仕様上の必然ではあります。
——その課題を解決するには、どのようなアプローチをとるべきでしょうか?
よく用いられる手法はモジュールをC言語やCythonなどで作成し、Python側から呼び出すことでパフォーマンスを向上させるという解決策です。実際、パフォーマンスを気にするサードパーティ製のPythonモジュールはC言語で書かれていることがよくあります。
——他には、どのようにしてパフォーマンスの最適化を図ることが可能ですか?
岡野 前提として、Webアプリケーションやバッチ処理などでパフォーマンスに問題が生じるのは、実際はプログラミング言語の処理速度ではなくI/O周りやSQLに課題があるケースが多い。
I/O待ちが原因でパフォーマンス劣化を引き起こしているならば、並列処理や非同期I/Oなどを用いて解決できるケースも多いです。原因を特定するには、アプリやサーバーの挙動を計測し、どの箇所がボトルネックになっているかを探ることが重要になります。
もしも調査をしたうえで、他の原因ではなく本当にPythonの処理速度が問題であると判明したならば、別の言語への切り替えや、PyPyなどパフォーマンスに優れた別のPython実装を使うアプローチも考えられます。
ただしPyPyは、私たちがよく使用している公式版のPython(CPython)とは当然ながら内部実装が異なっているので、機能の互換性に問題が生じることもあります。例えば、PyPyではガベージコレクションに関する参照カウントの挙動が異なるため、サードパーティのライブラリがPyPyと相性が悪いケースも見られます。
とはいえ本当は、全く別のアプローチもあって。パフォーマンス最適化のために一番最初にすべきは「要件を落とすこと」だと私は考えます。実装で解決する、というアプローチが必ずしも根本課題の解決ではないこともあります。もっと上流の段階で課題を解決した方が、設計や実装が楽になるケースも多いはずです。
影響の大きかった、2系から3系への非互換変更
——これまでPythonは、バージョンが上がるごとに数多くの機能変更がなされてきたと思います。なかでも、ユーザーにとって影響の大きかったものは?
岡野 思い当たるものは、いくつかあります。時系列順に話していくと、まず日本語に関する変更ですね。Pythonのバージョン2.3までは、日本語を扱いたい場合には追加でcodecsのインストールを行う必要があり、デフォルトでは日本語を扱うのに不都合がありました。バージョン2.4から各言語のcodecsが標準で入るようになり、当たり前のように日本語を使えるようになりました。
——日本のPythonユーザーにとっては、恩恵の大きい変更ですね。
岡野 その後に起きたユーザー影響の大きい変更としては、やはり2系から3系への移行が挙げられます。このメジャーバージョンアップではいくつかの非互換変更が入りましたが、特に文字列の扱いを変えたことには、かなりの既存コードが影響を受けました。
Pythonにおいては文字列をUnicodeとして扱う場合とバイト列として扱う場合とがありますが、2系では仮にUnicodeの文字列を引数として想定している箇所にバイト列の文字列を渡したとしても、ライブラリによっては暗黙的にデータを変換して、処理を通していたものがありました。
一方で、3系はそうした暗黙的な変換を排除し、Unicodeを想定した処理の場合はUnicodeのみを、バイト列を想定した処理の場合はバイト列のみを入力値として用いなければ、処理が動かないケースが多くなりました。文字列が持っていた仕様上の曖昧さを解消するために、Pythonの開発チームは非互換変更を入れてでも、3系で課題を解消したかったのではないかと推測します。
とはいえ、やはり2系から3系への移行にはかなりの歳月を要しました。バージョン3.0が出た段階では「その時点で移行するユーザーは、ほぼいないだろう」という想定をされてはいたのですが、その予想よりもずっと労力がかかりましたね。
バージョン3.0の登場時点における2系のバージョンは2.6で、3系への移行パスは示されてはいましたが、なかなか移行は進みませんでした。その後、バージョン3.1と2.7のリリースで追加の移行パスが示され、2系は2.7が最終バージョンであるとアナウンスされた経緯があります。
その後、3系の側でも2系の文法を部分的に取り入れるなどの対応を行ったり、2系と3系の両方で動くライブラリを開発するノウハウなども広まっていったりして、徐々に移行が進んでいくという流れがありました。
——その後に入った機能で、言語としての使い勝手に影響のあったものは?
岡野 最近入った機能を挙げると、型ヒント(typingモジュール)でしょうか。現時点ではそれほど普及してはいないものの、おそらく今後は広く使われていく機能だと思います。これは、ソースコード内の型に関する情報を静的解析するための機能です。
現在でもPyCharmなど一部のIDEは型ヒントの情報を使って型のチェックをしてくれますが、その恩恵を今後はサードパーティのライブラリなどでも受けられるようになっていくと思います。より、言語としての安全性や信頼性が高まっていくのではないでしょうか。
文法を複雑にせず、シンプルであれ
——こうした新しい機能は、どのような議論を経てPythonに導入されるのでしょうか?
岡野 基本的にPythonは文法の種類を増やさない、という方針を持つ言語ですから、さまざまな機能が「それ、いいね」とすんなり入るケースはほとんどありません。とりわけ、新しい文法を入れることには慎重で、話し合いは長期に及びます。
新機能や機能変更を提案する際には、まずPEP(Python Enhancement Proposal)と呼ばれる提案内容と技術仕様を書かなければいけません。その仕様を誰もが閲覧できる状態にして、長期にわたり議論した後に、最終的には意思決定の権限を持ったメンバーが入れる・入れないの判断をします。これはPythonコミュニティの特徴的な点ですね。
——Pythonが持つ“文化”のようなものを、私たちはどのように学ぶべきでしょうか?
岡野 Pythonには「The Zen of Python」と呼ばれるテキストがありまして、これはPythonという言語が持つ特性や思想を簡潔にまとめたもので、Pythonインタプリタ上で「import this」と入力すると全文を表示できます。この内容を読んで、Pythonがどのような価値観のもとに成り立っているかを学ぶといいと思います。
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Tim Peters「The Zen of Python」より引用
Pythonならば、さまざまな課題をより楽に解決できる
——最後に伺いたいのですが、現代のエンジニアにとってPythonを選択する意義とは何だと考えられますか?
岡野 近年でいえば、Pythonを選ぶ理由として有力になってきたのは、いわゆるデータ分析、データサイエンスの領域におけるライブラリが相当に充実していることですね。そうした領域を扱ううえでは、Pythonはデファクトの言語になっていると思いますし、他の言語をあえて選ぶ理由は薄くなっていると感じます。
私はそれ以外の領域でもPythonを用いることが多いですが、その理由は「より楽に課題解決できるから」です。何か業務において解決したい課題があるときに、「Pythonであればその領域に対応できるライブラリが数多くあり、他の言語よりも課題を容易に解決できるケースが多い」ということに尽きます。
もちろんPythonも万能ではなく、スマートフォンアプリやデスクトップアプリの開発などに最適かというと、そうではない。それでも適用可能な領域は非常に幅広いです。プログラミングの本質はなにかしらの課題解決だと思いますが、その実現手段としてとても優れた言語だからこそ、私はPythonを愛用し続けています。
取材・執筆:中薗昴