Python 2/3 両対応のために `unicode_literals` を使うべきか

背景

Python 2 用のコードを書くときは、 Python 3 対応を見越して

# -*- coding: utf-8 -*-
from __future__ import division, print_function, absolute_import

をテンプレとして書いています。 __future__ はファイルごとにバラバラだと混乱を招くので、今関わってるプロジェクトでもこれを新規ファイルのテンプレとして登録してもらってます。

Python 3 の構文、リテラルを有効にする __future__ のうち、 unicode_literals だけは今まで使っていなかったのですが、ふと「あ、やっぱり使うべきだな」と思いついたので、そのへんをまとめます。

第三の文字列型 native string

Python 2 には2つの文字列型 str (bytes) と unicode がありました。Python 3 では unicodestr になり、唯一の文字列型になりました。 str は 2/3 で意味が違うので、以後この2つを bytes, unicode と呼びます。

unicode_literals は prefix 無しの文字列リテラルbytes ではなく unicode にするものです。 bytes をバイト列が必要なときだけに利用し、文字列は全て unicode にすることで、 Python 3 への移行がスムースになります。

しかし、ここで、実際には存在しない第三の文字列型 native string (以降 native と呼びます) が登場します。 native とは、 Python 2 では bytes, Python 3 では unicode として扱う文字列です。例えば、 __repr____str__ が返すべき文字列型であったり、 def foo したあとに foo.__name__ で得られる文字列型が native になります。

Python 2 において、 ASCII 文字だけで構成される文字列は bytes から unicode への自動変換が働き、比較やハッシュにも互換性が保たれているので、普段 native を意識する必要はあまりありません。例えば次のコードは全く問題なく動きます。

>>> registry = {}
>>> def register(func):
...     registry[func.__name__] = func
...     return func
...
>>> @register
... def foo():
...     print('foo')
...
>>> registry[u'foo']()
foo
>>>

しかし、 PEP 3333 (WSGI 1.0.1) で native string の定義が導入されたことにより、 native の重要性がましてしまいました。また、 Python 3.3 未満では、 unicode を使うと ASCII 文字でも1文字2バイトあるいは4バイト消費してしまうので、効率を気にする人は Python 2 では unicode ではなく bytes を使いたい場合があります。

Django 方式と Flask 方式

WSGI に対応する Web フレームワークPython 3 に対応するために unicode_literals を使う Django 方式と、使わない Flask 方式が存在します。

文字列型 Django 方式 Flask 方式
bytes b'foo' b'foo'
unicode 'foo' u'foo'
native str('foo') 'foo'

Django 方式は native を生成するときに str() の呼び出しが発生しますが、実際に native が必要な場面は相当限られているので、あまり問題にならないでしょう。

Django 方式のメリットは、 u プリフィックスがない Python 3.2 をサポートできるところで、 Flask 方式のメリットは普段何気なく利用している ASCII だけの文字列リテラルが native になることで Python 2 でも 3.3 以降でも1文字1バイトの効率が実現できることです。

Flask アプリケーションではどうか

いままで、 Web フレームワークには Flask 使ってるから Flask に合わせるというのと、やっぱり効率が気になるという理由で、 Flask 方式を採用していました。Python 3 を本格的に使うのは 3.3 以降なので、 Django 方式の Python 3.2 をサポートできるというメリットは意味がありません。

しかし、 Flask の API は、文字列型には全て unicode を利用しており、直接 WSGI を叩かないアプリケーションは native を必要としません。 効率も、データとしての文字列は unicode で統一するのが当たり前で、 native を使うのは docstring とか hasattr/getattr を使うときとか、 JSON の key とか、細かい部分だけなので問題ないはずです。どうせ今何かの理由で Python 2 を使っていてもすぐに Python 3 に移行して問題なくなるし、そもそもそんなに効率にシビアなアプリを書くなら Go を使いましょう。なので、 (Flask アプリケーションであっても) Flask 方式にも大したメリットがないのです。

また、会社でアプリケーションを作るときは、フレームワークの開発とは別の unicode_literals のメリットがあります。英語が苦手な開発者の生産性を落としてまで docstring やデバッグ用のログを ASCII 文字に限定したくないのですが、 Django 方式を使うと非ASCII文字を u プリフィックスなしで書いてしまって不意に UnicodeDecodeError を起こすリスクを避けられるのです。「非ASCII文字を使うときは u をつける」という条件付きコーディングルールが必要無くなりコーディングルールがシンプルになるというメリットもあります。

終わりに

ということで、今後は unicode_literals を使おうと思います。とはいえ、今のプロジェクトを途中から変えるべきかどうか不明ですし、次のプロジェクトは Python 3.4 を使いたいので、「次」が無いかもしれませんが。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。