Django と Python 3 - #python_adv
Django-ja の方からきました。こんにちわ。さて、昨日の Ian 先生のブログにも書いてある通り、ついに Django にも本格的に Python 3 の足音が近づいてきました。ただし現在 alpha 版が公開されている 1.5 では "実験的" なサポートで、1.6 以降で正式にサポートする予定となっています。あくまでも "実験的" であり、プロダクションでの利用は "非推奨" となっています。Django コミュニティでは、この 1.5 でサポートをテストしてもらい、そのフィードバックを呼びかけています。なのでプロダクションでの利用は 1.6 まで待ちでしょうね。また Python 3 サポートと同時に、Django 1.4 では Python 2.4 がサポートから外れ、1.5 では Python 2.5 がサポートから外れます。これで 1.7 以降から django.utils
配下が軽くなっていくんでしょうかね (現在は 3 サポートのためにさらに増えてる)。
バージョン | 下位互換 | Python サポート | ||||
---|---|---|---|---|---|---|
2.5 | 2.6 | 2.7 | 3.2 | 3.3 | ||
Django 1.4 | > 1.2 | ○ | ○ | ○ | - | - |
Django 1.5 | > 1.3 | - | >= 2.6.5 | ○ | △ | △ |
Django 1.6 | > 1.4 | - | >= 2.6.5 | ○ | ○ | ○ |
Python 2.5 系を利用している場合は、1.6 のリリースまで (2013 後半ぐらい?) に Python 2.6.5 以上 (2.7.3 以上を強く推奨) への移行が必要です。1.5 は今月 (2012/12) 中にリリース予定とのことなので、今のうちに Python 3 へ移行方法を抑えときたいなと。以下 Django ドキュメントの翻訳作業がてら "Porting to Python 3" を元にご紹介。さすが Django のドキュメントだけあって Python 3 のお勉強にも良い感じでした。
Django は同じソースコードで Python 2 (>= 2.6.5)、3 (>= 3.2) 双方で動作するよう six
という互換用レイヤーを利用しています。six
による Python 3 対応については、Python ドキュメント "Python 2 から Python 3 への移植" に詳しく書いてあります。
思想
Python 2 と Python 3 の違い、変更については Python ドキュメントや去年 (2012) のアドベントカレンダー等を参考にしてください。
Django は Python 2, 3 互換ですが、作成するアプリケーション自体は必ずしも互換性を持たせる必要はありません。プラガブルなアプリケーションの作成者は Django 同様の移植が推奨されています。Python >= 2.6 以降を対称にすると、より簡単に互換性を持たせることができます。
Django 1.5 より django.utils.six
のような互換性ツールが導入されます。上位互換性のために Django 1.4.2 以降、互換性ツールへのエイリアスが用意されています。アプリケーションでこれら互換性ツールを利用する場合は Django 1.4.2 以降が必要です。
当然、互換性があるソースコードを書くのはコストがかかります。Django の開発者は、Python 3 互換の Python 2 コードを書くより、Python 2 と互換の Python 3 コードを書く方が価値が高いと判断しました。これはコードの寿命が延びるだけでなく、Python 3 の利点 (より正確な文字列の取り扱い等) の恩恵を受けることができます。Python 2 は下位互換でサポートされます。
Django が提供する移植用のツールはこれらの思想に基づいています。
移植のテクニック
>>> ユニコードリテラル
このステップは
- Python モジュールの先頭に
from __future__ import unicode_literals
を追加 - すべてのモジュールにそれぞれ追加するのが最適です - ユニコード文字列の接頭辞
u
を削除する - バイト文字列の接頭辞
b
を追加
体系的にこの変更を実施すると下位互換を保証することができます。しかし、Django はユニコードのインターフェスを公開して以降、バイト文字列は必要とされていません。Python 3 はバイナリデータ、バイト指向インターフェスを除いて原則として文字列はユニコードを利用します。Python 2 では、アスキーデータのみの場合に限り、バイト文字列とユニコード文字列が変換可能です。可能な限り接頭辞 b
を避け、ユニコード文字列を利用するようにします。
>>> 文字列処理
Python 2 の unicode
型は Python 3 の str
、str
は bytes
に置き換えられ、basestring
は無くなりました。six
はこれらの変更に対応するためのツールを提供しています。Django にも django.utils.encoding
と django.utils.safestring
モジュールにより、いくつかの文字列に関するクラスと関数を提供しています。そのクラス名、関数名には Python 2 と Python 3 で意味が異なる str
、Python 3 にはない unicode
が利用されていました。曖昧さの回避、及び明確化のため bytes
と text
と名前を変更しました。
django.utils.encoding
の名前は以下のように変更となっています。
旧 | 新 |
---|---|
smart_str | smart_bytes |
smart_unicode | smart_text |
force_unicode | force_text |
下位互換のため、旧関数も Python 2 上で動作します。Python 3 上では、smart_str
は smart_text
のエイリアスとなっています。また、上位互換のため、新関数は Django 1.4.2 以降で動作します。
django.utils.safestring
は主に、mark_safe()
と mark_for_escaping()
関数を通して利用されており、それに変わりはありません。内部で直接呼び出している場合は以下のように変更となります。
旧 | 新 |
---|---|
EscapeString | EscapeBytes |
EscapeUnicode | EscapeText |
SafeString | SafeBytes |
SafeUnicode | SafeText |
下位互換のため、旧関数も Python 2 上で動作します。Python 3 上では、EscapeString
と SafeString
はそれぞれ EscapeText
と SafeText
のエイリアスとなっています。また、上位互換のため、新クラスは Django 1.4.2 以降で動作します。
>>> __str__()
と __unicode__()
メソッド
Python 2 では、オブジェクトは __str__()
と __unicode__()
というメソッドを指定します。これらのメソッドはそれぞれ str
(bytes
) と unicode
(text
) を返します。
print
構文やビルトイン関数の str()
は __str__()
をコールし、オブジェクトを可読可能にします。ビルトイン関数の unicode()
は __unicode__()
があればコールし、ない場合は __str__()
の結果をシステムエンコーディングでデコードした結果を返します。逆に Django の Model を継承したクラスは __str__()
は __unicode__()
を UTF-8 でエンコーディングした結果を自動的に返します。
Python 3 には、 str
を返す __str__()
しかありません。( __bytes__()
を定義することもできますが、bytes
の取扱いが非常に難しいため Django アプリケーションではほとんどこのメソッドを利用しません)
Django では Python 2 と 3 で __str__()
は __unicode__()
メソッドを定義できるシンプルな方法が提供されています: 文字列を返す メソッドを定義し、python_2_unicode_compatible() デコレータを適用します。
Python 3 上では、このデコレータは何も処理されません。Python 2 では、適切な __str__()
と __unicode__()
メソッドを定義します (オリジナルの __str__()
メソッドを置き換えます)。たとえば、以下のようになります:
from __future__ import unicode_literals from django.utils.encoding import python_2_unicode_compatible @python_2_unicode_compatible class MyClass(object): def __str__(self): return "Instance of my class"
このテクニックは Django の移植における思想が反映されています。
上位互換のため、このデコレータは Django 1.4.2 以降で動作します。
最後に、__repr__()
は Python のすべてのバージョンで str
を返さなければならないので、注意が必要です。
>>> dict
と dict
ライクなクラス
Python 2 では dict.keys()
、 dict.items()
、 dict.values()
は list
を返しますが、Python 3 ではイテレータを返します。django.utils.datastructures
に定義されている QueryDict
と dict
ライクなクラスは Python 3 と同様の動作をします。
six
はこの変更と互換の iterkeys()
、 iteritems()
、 itervalues()
関数を提供しています。Django にはそれに加え MultiValueDict
とサブクラス用に iterlists()
関数が追加されています。
>>> HttpRequest と HttpResponse オブジェクト
- ヘッダーは常に
str
オブジェクト - インプットとアウトプットオブジェクトは常に
bytes
オブジェクト
具体的には、HttpResponse.content
は bytes
オブジェクトが含まれています。テストの際に str
と比較すると問題が発生するかもしれません。この解決策は assertContains()
と assertNotContains()
を利用します。これらのメソッドはレスポンスとユニコード文字列を引数にとることができます。
コーディングガイドライン
このガイドラインは Django のソースコードに適用されます。同様の移植手法を選択するサードパーティアプリケーションにも推奨されます。
>>> シンタックス要件
ユニコード
Python 3 においては、すべての文字列はデフォルトでユニコードとみなされます。Python 2 の unicode
型は Python 3 では str
、str
は bytes
となります。Python 3.2 ではシンタックスエラーとなるため、ユニコード文字列の接頭辞 u
は使用しません。バイト文字列には接頭辞 b
をつける必要があります。Python 2 でも同様の振る舞いを可能とするため __future__
から unicode_literals
をインポートする必要があります。
from __future__ import unicode_literals my_string = "This is an unicode literal" my_bytestring = b"This is a bytestring"
もし Python 2 においてバイト文字列リテラルが必要な場合や、Python 3 においてユニコード文字列リテラルが必要な場合は、ビルトインの str()
を用います:
str('my string')
Python 3 では、str
と bytes
は自動的に変換されず、codecs
モジュールはより厳格になりました。str.decode()
は常に bytes
を返し、bytes.decode()
は常に str
を返します。
その結果、時には以下のようなパターンが必要となります:
value = value.encode('ascii', 'ignore').decode('ascii')
バイト文字列へのインデックスには注意が必要です。
例外
例外を受ける際は、以下のキーワードを追加います:
try: ... except MyException as exc: ...
以下の旧シンタックスは Python 3 で削除されました:
try: ... except MyException, exc: # Don't do that! ...
トレースバックの変更等で、再度例外を投げる場合のシンタックスは、 six.reraise()
を使用します。
>>> 特殊メソッド
Python 3 で変更された特殊メソッドを使用するために、以下のパターンを使用します:
イテレータ
class MyIterator(six.Iterator): def __iter__(self): return self # implement some logic here def __next__(self): raise StopIteration # implement some logic here
真偽値
class MyBoolean(object): def __bool__(self): return True # implement some logic here def __nonzero__(self): # Python 2 compatibility return type(self).__bool__(self)
除算
class MyDivisible(object): def __truediv__(self, other): return self / other # implement some logic here def __div__(self, other): # Python 2 compatibility return type(self).__truediv__(self, other) def __itruediv__(self, other): return self // other # implement some logic here def __idiv__(self, other): # Python 2 compatibility return type(self).__itruediv__(self, other)
>>> six
で互換性のあるコードを書く
six
は、Python 2 と Python 3 を単一のコードベースでサポートするための標準的な互換性ライブラリです。まず、six のドキュメントを読んでください。
six
は、バージョン 1.4.2 以降の Django にバンドルされています。それは django.utils.six
でインポートできます。互換性があるコードを書くための最も基本的な変更は以下の通りです。
文字列処理
basestring
と unicode
型は Python 3 から削除され、 str
の意味が変更されました。これらの型のテストには以下の方法を使用します:
isinstance(myvalue, six.string_types) # replacement for basestring isinstance(myvalue, six.text_type) # replacement for unicode isinstance(myvalue, bytes) # replacement for str
Python >= 2.6 は bytes
が str
のエイリアスとして提供されているため six.binary_type
は必要ありません。
長整数 (long)
Python 3 に long
型はありません。1L
はシンタックスエラーとなります。six.integer_types
により、値が整数型か長整数型か調べることができます。
isinstance(myvalue, six.integer_types) # replacement for (int, long)
xrange
xrange
を使用する際は、 six.moves.xrange()
をインポートします。
モジュールの移動
Python 3 ではいくつかのモジュール名が変更となりました。django.utils.six.moves
モジュールにより、インポートに互換性を持たせることができます。
urllib
と urllib2
は一から作り直されており、django.utils.six.moves
では処理されません。Django では、以下のように明示的に両方からインポートを試みます:
try: from urllib.parse import urlparse, urlunparse except ImportError: # Python 2 from urlparse import urlparse, urlunparse
PY3
Python 2 と Python 3 で異なるコードが必要な場合は six.PY3
で確認します:
if six.PY3: # do stuff Python 3-wise else: # do stuff Python 2-wise
これは six
で解決できない場合の最終手段です。
>>> six
のカスタマイズ
Django にバンドルされている six
には、いくつかの関数が追加されています:
iterlists
(MultiValueDict)
MultiValueDict
の値をリストとするイテレータを返します。これは Python 2 ではiterlists()
ですが、Python 3 ではlists
に置き換わっています。
assertRaisesRegex
(testcase, *args, **kwargs)
これは Python 2 の
testcase.assertRaisesRegexp
を Python 3 のtestcase.assertRaisesRegex
に置き換えます。まだ現行の Python 3 にはassertRaisesRegexp
はありますが、警告がでます。
Django バージョンの six
はデフォルトに加え、 thread
を _thread
、 dummy_thread
を _dummy_thread
に置き換えます。
--
良くも悪くも Django らしい Python 3 対応ですね。というか WAF についてほとんど触れてない気がします。ごめんなさい。これを読んで Django の思想やドキュメントの素敵さが紹介できればと思います。Django-ja ではこの素敵なドキュメントの翻訳にご協力してくださる方を絶賛募集中なのでぜひ!Django ネタが続きましたが、明日は @methane 先生による flask のネタです。
コメント
コメントを投稿