POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

Brett Cannon

本記事は、原著者の許諾のもとに翻訳・掲載しております。

12月、私は PuPPy(the Puget Sound Python users group)の会合でQ&A セッション を行いました。そこでようやくPython 3が誕生した理由と、string/bytesに関する全てを説明しました。Python 3が作られた理由をユーザはもう知っているはずだと思っていたので、私はこの説明で称賛を得たことに、ちょっと驚きました。後で考えてみると、Pythonに詳しい人もそうでない人も含めて大多数の人が、その理由を探すように言われたり、好奇心からその理由を探し当てられるなどと考えた私が愚かでした。ですから、このブログの記事で、Python 3が存在する理由をわかりやすく説明します。後方互換性の全くない unicode / str / bytes の仕様変更は、Python 3のコードの移植の中でも本当に難解な部分ですので、私たちがその仕様変更を選択した理由を特に説明します。

Python 2におけるテキストデータとバイナリデータの混乱

さっそくですが、次のリテラルは、どんな意味を表していますか?

'abcd'

Python 3ユーザなら、文字”a”、”b”、”c”、”d”がその順番に並んだ文字列だと答えるでしょう。

一方、Python 2ユーザなら、同じ答えをする人と、97、98、99、100を表すbytesだと答える人がいるかもしれません。実は、Python 2では、 str オブジェクトが表すものには正解が2つあるのです。それが、Python 3の答えが唯一の正解になるように言語仕様を変更した理由です。

Zen of Python には、「あることを行う方法は、わかりやすく、できれば1つだけにすること」と書かれています。Pythonの言語の中に、テキストデータとバイナリデータのどちらも表すことができるリテラルがあることは、問題となっていました。例えば、ネットワークから何かを読み込んで str オブジェクトを返した場合、そのオブジェクトがバイナリデータとテキストデータのどちらを表しているのかは、とても慎重に判断しなければならないでしょう。なぜなら、オブジェクトがあなたの元から一旦離れたら、それを知る方法はないからです。さもないと、その str オブジェクトをテキストデータまたは全く別のデータに変換するはずのコードに、バグが発生する可能性があります。しかし、たまたまデータを変換する処理を通らなかった場合、 str オブジェクトが潜在的に2つの異なる意味の型を表すがために起こる、このような見落としがあっても、気づくのは困難だったのです。

そこで、テキストデータには str 型を使わず、代わりに unicode 型を用いれば、この問題はPython 2でもすべて解決できると主張する人がいるかもしれません。厳密にはそのとおりですが、実際にそうする人はいません。ものぐさな人は、Unicodeにデコードするような余分な作業はやりたがりません。また、パフォーマンスにこだわる人は、デコードの負荷を敬遠します。いずれにせよ、混乱しないように十分配慮してコーディングすることが前提として必要ですし、人間は完璧ではないので誤りを犯す可能性があることは周知のとおりです。もし、Python 2でバグのないコードを作成するという願いが実際にかなっていれば、私は、次のようなことを絶えず聞かされることはなかったでしょう。実は、Python 3にプログラムを移植したほとんどすべての人が、テキストデータとバイナリデータのエンコードとデコードに関して潜在的なバグがあったと言っています。

バグを回避する重大なポイントで、皆さんが忘れていることがあります。それは、言語を簡素化することと、 str オブジェクトが表す可能性のある暗黙的なデータを除去することです。そうすることによって、バグが発生しにくいコードになるのです。Zen of Pythonには、「暗黙的より明示的なほうがいい」と書かれています。その理由は、伝わりにくいコードの曖昧さや暗黙の知識は間違えやすいし、バグにつながるからです。バイナリデータとテキストデータを明示的に分けるよう開発者に強制することで、特定のバグを持つ可能性の少ない、優れたコードになるのです。

外の世界ではUnicodeの全面的なサポートが進んでいました(正当な理由で)

Pythonがどのくらい古い言語か忘れられていることが時々あります。Guidoは1989年12月にPythonのコーディングを始め、1991年2月にオープンソースとして最初のリリースを行いました。それはつまり、 1991年10月に出版されたUnicode 1.0, Vol.1 よりもPython自体が先だったということです。その後の年月、Unicodeの標準化後に作成された言語では、文字列の実装はUnicodeをサポートできるエンコーディングをベースとすることを選択しました。これでPython 2は不幸な立場に置かれましたが、2004年には(Python 3の計画が始まって)立場はずっとよくなりました。しかしPython 2のUnicodeサポートがもっとも貧弱であることはほぼ間違いありませんでした。 unicode 型は完全に任意で、ユーザが全てのテキストデータに対して使用するとは限らなかったからです。

Unicodeをサポートすることであらゆる書き文字をサポートできることは重要です。Pythonは世界共通の言語であり、ASCIIがカバーしているローマ字のアルファベットをサポートするだけの言語ではありません。そういうわけで、Python 3はテキストに関しては「Unicodeを絶対サポートする」とします。それにより、開発者がコードを書くときに明確に意図するかどうかに関わらず、Python 3の全てのコードは世界中の全ての人をサポートするということが保証されます。Python 2では、テキストデータのために unicode 型を適切にサポートする時間をかけたプロジェクトとそうでないものの間に深い溝があります。Python 3ではそのような溝はありませんし、すべての言語のサポートが負担なく行えます。

Pythonの人気は今後も上がり続けるだろう、と考えて決断しました

2004年に私たちは PEP 3100 をスタートし、Python 3の設計を始めました。(余談:このPEPの元のナンバーは3000だったのですが、3100に再ナンバリングしました。 3000ナンバーのPEP にはPython 3の開発をどう実施するかを記述できるようにしたのです)私たちは、Pythonの人気が上向きなのを知っていましたし、その成長が(運よく☺マーク付きで)続くことを望みました。しかしそれはまた、Pythonの人気を続かせようとするならば、後でと言わず今、いかなる設計ミスも修正する必要があるということを意味していました。Python 2.7がレガシープロジェクトだけで使われて新規には使われないとなったら、Python 2より長く続くだろうし使用は増えるだろうからPython 3が失敗することはないと考えて、私たちはPython 3ではPython 2より多くのコードが十分に長い時間をかけて書かれると想定しました。そしてこの想定のもと、私たちはPython 2からPython 3への移行という痛みに耐えることを決め、Python 3を開発しました。もちろん、「世界中で書かれたコードの総行数」という観点でPython 3のコードがPython 2のコードを上回るのが確認できるまでには数十年かかるでしょう。

このような後方互換性のない変更は二度と行いません

私たちはチームとして、 unicode / str / bytes のように大きな変更を唐突に実施することは二度とないと決めています。Python 3をスタートした時、私たちは、コミュニティはPython開発側がしたようにPython 2に見切りをつけ、Python 2をサポートする最後の機能をリリースし、Python 2用だけのバグ修正をリリースするかたわらPython 3の機能を開発するためにPython 3に移行すると思って/期待していました。期待していたことが起こらなかったのは明らかですし、私たちは教訓を得ています。加えて、これほど大きな変更の必要性を正当化できたこの言語ですが、基本設計には何の欠陥もありませんでした。ですから、Python 4では標準ライブラリから廃止予定のモジュールを削除するより思い切ったことは何もしないと思っていてください。

まとめ

これが今のPython 3の状況に至った次第です。Python 2では str 型をオーバーロードしたせいで出来たバグの山があることを私たちは認識していました。そこでPython 3ではテキストデータとバイナリデータを明確に区別するよう改修しました。全てのテキストデータを自動的にUnicodeとして扱うようにしたことも、多言語での開発を行うプロジェクトを急にやりやすくしました。そして、私たちは早ければ早いほどいいと考えて仕様変更を行いました。私たちはコミュニティがPython 2を後にして私たちについてきてくれるだろうと思いつつPython 3を構築しましたが、思ったとおりにはいかないことがわかりました。それどころかもうしばらく時間がかかりそうで、Python 2/3に互換性のある言語サブセットをPython 2からPython 3への移行に対処するために使っているのです。