kngenieの日記

日記だ。でもさすがに毎日は書けない。

なぜPython3にはbytes.formatがないのか

1年ぐらい前に書いてドラフト箱にほったらかしになっていた。もったいないので公開する。

ずっとPython 2.7をメインにしていて特に問題もなかったのだが、最近周りがPython 3.4をターゲットにコードを書き始めていて、私の提供するライブラリが2.7しかサポートしていないと知ると悲しい顔をされるようになったので、Python 3互換にする作業をしている。かーなり面倒な作業である。いかにPython 2で文字列の扱いがいい加減だったかを思い知らされる。

Python 3互換にする上で一番苦労するのが、

  • bytes.format()がない
  • WSGI周りの仕様変更

ではないか。他はsixなどが便利に対応してくれるのだが、この二つは多くの変更を強いられる。とくに後者はfrom __future__ import unicode_literals入れときゃ後で楽、と思っていたのがバックファイアしていて痛い。

bytes.formatがないのはPython 2であたりまえだったstr/unicodeの考えなしの混用を避けるためのものだと思っていたが、Webアーカイブを扱っていると、intを文字で出力する場合など、「なんでstrで作ってからencodeしなけりゃいけないんだよ」と思うこともある。これは多分にstrとunicodeがほぼ交換可能なものとして存在したPython 2のせいで、Javaのように始めからStringとbyte[]がまったく別物であるなら悩みもしないのだが。

というところで、同僚が Issue 3982: support .format for bytes - Python tracker のことを教えてくれた。2008年9月27日に始まった「Python 3にbytes.format()を追加してくれ」というissue itemなのだが、最新のコメントは2016年6月10日である。

議論を総括すると、bytes.format()はネットワークプロトコルやファイルフォーマットを扱う上でとても便利である(というか便利すぎる)ということだ。テキスト処理のオモチャプログラムを書いているのならともかく、実世界ではほとんどのソフトウェアはネットワークプロトコルやファイルフォーマットを扱うので、Python 2にbytes.format()があったことはコードの可読性に大きく貢献した。一応Python 3にもstruct.packがあるのだが、bytes.format()に比べると機能不足が否めない。

http://bugs.python.org/issue3982#msg171804 にSerhiy Storchakaが説明しているように、str.format()bytes.format()は要件が大きく違っていて、同じメソッド名であるべきかどうかも疑問である。struct.packを拡張した方がいいのではないか、という提案もうなづける。ただ、テキストベースのプロトコルやファイルフォーマットが当たり前な昨今、struct.packという名前はあまり感じがよくないし、http://bugs.python.org/msg180423 でGuidoが書いているように、問題は主にPython 2/3両互換のコードを書くことなのだから、メソッド名が違うと解決にならない。

Python 3.3ではasciilatin1へのencodeはmemcpyと同程度に高速である。以前のバージョンに比べて12倍以上速いという。これはPEP-393に見るように、Pythonの文字列表現がASCIIに収まる文字列に対して最適化が行われているためだろう。これを根拠に「str.format()してからencode()しろ」という意見が当初は大勢を占めた。

Python 3のstr.format()はunicodeを前提に作り込まれていて、__format__による拡張など、bytes.format()の実装を面倒にするポイントが多々あるようだ。ただ、これはGuidoが指摘するように、bytes.format()はベーシックなデータタイプしかサポートせず、__format__も使わない、とすればいいだけのように思える。

bytes.format反対が優勢の議論はGuidoがTwistedのニーズを紹介したあと、Glyph Lefkowitzが議論に参加して変化を見せる。http://bugs.python.org/issue3982#msg180447 に至って、Terryがbytes用の限定的formattingの必要性を理解を示した。それまでstr.formatと同じものを実装することを求められていると思っていたらしいのが、Python 2/3両互換のために限定的な機能を用意すればいいだけだ、という共通理解が形成されていく。さらにformatより__mod__(いわゆる%-formatting)にするべきという意見も出てくる。確かにformatPython 2に導入されたのはつい最近なので、__mode__を使っているケースの方が多い。

結局Antoine Pitrouによるbytes.format()の試験的な実装提案(PEP-460 Add binary interpolation and formatting, 2014年1月6日)は
同時期に起案されたPEP-461 (bytesとbytearrayに%-formttingを追加)が同年3月に受理されたのを機に取り下げとなり、PEP-460がPython 3.5に含まれることになった。

これをもって2014年7月26日にこの案件は"won't fix"でcloseする。

これで決着したように見えたが、今年5月になってGregory P. Smithが、やっぱりbytes.format()が欲しいよ、というコメントをポストする。mini-languageはformatの方が強力なので、それが便利だという。

それに答えて6月10日のNick Coghlanのコメントは要約するとこういうことを言ってる。

  • str.formatの威力は拡張性のある__format__プロトコルformat組み込み関数によるところが大きく、これらが文字列操作に特化した実装になっているため、bytesのサポートは難しい。
  • %-formattingに対する論調は当初から変化している。最初「%はいずれ廃止されるのでformatを使ってくれ」だったのが「両方ともフルにサポートされている」に変わり、さらに今では「文字列にはformat、バイナリには%」になった。
  • 当初の「%を使うのはやめて」というガイダンスに従った人たちには申し訳ないが、また%に戻ってもらう必要がある。

というわけで、bytes.format()にはならなかったけど、bytes用の%-formattingが復活するという決着になった。
もっとも実際のところ現在使われているPython 3は3.4で、これにはbytes.formatもbytes.__mod__もないのだから、Python 3.4をしばらくサポートせねばならない状況下では3.5でbytes.__mod__が導入されても、当初の要件であったPython 2/3両互換はそれほど楽にはならない。正直遅きに失したという感じである。

Python 3.5の%-formattingはbytesのinterpolationに%bunicode文字列(str)のinterpolationには%aを使う。Python 2との互換性のために%s, %rもサポートされている。%aはオブジェクトに対してはrepr(object).encode('ascii')と同等で、オブジェクトが__bytes__を実装していれば、直接のinterpolationも可能。

途中で出てきたbytes.format()のために__bformat__を追加するという案よりは、自然な形に落ち着いたのかな、という印象だ。

もっと詳しい情報