4. 2to3
の先に¶
2to3
は優れたツールですが、python3 セーフにするためには、
もう少し手を加える必要がある場合もあります。
特に制御関係ではバイナリデータを取り扱う場合に注意が必要です。
4.1. 文字型/バイナリ型データ¶
Python2ではC言語からの伝統を受け継いで(?)、文字型データ(str
)とバイト型データ(bytes
)には
本質的な違いはありませんでした。というか、bytes
はstr
の別名となっています。
また、2byte文字を取り扱うために、unicode型が導入されています。
unicode型とstr
型の変換はencode/decodeなどのメソッドを使って行います。
ところが、Python3では
文字列(str
)は unicode型の同意語となります。unicode文字列を表すバイト型データ(bytes
)はunicode文字列をencodeすることで得られます。
また、バイト列をunicode文字列(str
)に変換するためには、decodeメソッドの助けを借りなければいけません。
>>> ("あ",u"あ", r"あ",b"\xe3\x81\x82")
("あ",u"あ", r"あ",b"\xe3\x81\x82")
('\xe3\x81\x82', u'\u3042', '\xe3\x81\x82', '\xe3\x81\x82')
>>> s="あいう"
s="あいう"
>>> type(s)
type(s)
<type 'str'>
>>> u=s.decode('utf-8')
u=s.decode('utf-8')
>>> type(u)
type(u)
<type 'unicode'>
>>> u
u
u'\u3042\u3044\u3046'
>>> s
s
'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'
>>>
>>> ("あ",u"あ", r"あ",b"\xe3\x81\x82")
("あ",u"あ", r"あ",b"\xe3\x81\x82")
('あ', 'あ', 'あ', b'\xe3\x81\x82')
>>>s="あいう"
s="あいう"
>>> type(s)
type(s)
<class 'str'>
>>> b=s.encode('utf-8')
b=s.encode('utf-8')
>>> type(b)
type(b)
<class 'bytes'>
>>> b
b
b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'
>>>
Vers. |
python2 |
python3 |
||||
---|---|---|---|---|---|---|
types |
encode |
decode |
[0] |
encode |
decode |
[0] |
bytes |
->str(b) |
->u |
str |
N/A |
->str(u) |
int |
str |
->str(b) |
-> u |
str |
->b |
N/A |
u |
unicode |
->str(b) |
-> u |
u |
->b |
N/A |
u |
通常の文字型データだけを取り扱っている限りでは、2to3
の変換後のプログラムは問題なく動作するでしょう。しかしPython2でstr
型データをbytes
型データとして取り扱うために使っているプログラムでは、2to3
は自動変換を行うことができません。str
型データとして取り扱うべきか(その場合にはUnicode型=python3のstr
型に変換),bytes
型データとして取り扱うべきか(その場合にはPython3では明示的にbytes
型を指定する必要がある)をpython2のソースから自動的に判断することが 2to3
にはできないためです。
py2/p3で両立するためには、bytes
とunicodeを明示的に使い、
bytes
-> unicodeの時にdecodeを、unicode-> bytes
の変換でencodeを使うと決めておけば、
2to3
はそれを頼りに変換を行います。
2to3
適用前にこれを実施しておけば、あとは何度2to3
を通しても問題無いはずです。
文字列として使う場合は、unicode
バイト列として使う場合は、
bytes
cVXI11
やEPICS CA
モジュールではpython2/python3の双方をサポートするために、この様な変更を加えています。
また、cVXI11
やEPICS CA
モジュールはsetup ツールを使ったbuild時に2to3
ツールを自動的に適用する様にしています。 urllib
のurlopen()
などから返される値も:py:class:`bytes
型のデータとなっていますので、
テキストとして処理する前に:py:meth:`decode メソッドを使って str
に変換して置きましょう。
リテラルなバイト列表現 (b"abc"
など)はPython2, Python3の双方で利用可能です。
バイト列として取り扱う定数データは、この表現にして置くことで、python2/python3の双方でほぼ同じ様に取り扱うことができます。
バイト列(byte)から1バイトのデータを取り出す際には、注意が必要です。Python2では[n]オペレータで1バイトのデータをバイト列(=str
)を
取り出すことができます。しかし、python3ではbyte列から[n]オペレータを使って1バイトデータを取り出すと、int型のデータとなってしまいます。
一方[n:n+1]で一バイト分のデータを取り出した場合には、bytes
型になっています。 この様な動作の違いを吸収するためには、
python2でも1バイトのデータを取り出す場合にも[n:n+1]を使っておくという配慮が必要です。
また、python2/python3とも bytearray 型を持っています。bytearray型はPY2/PY3で概ね同じmethodsを持っている(例外:copy,clear,maketrans)ので、 byte型ではなく、bytearray型を積極的に使うのもpython2/pytho3互換とするためには有効と考えられます。
4.1.1. openのencoding¶
文字型/bytes
列の取扱で注意すべき事として、open関数のpython2/python3での
違いがあります。
python3において、文字型データのファイル入出力では、入出力モードがテキストモード("r"あるいは"w")である場合には、ファイルが期待するエンコーディングへの変換が必要となります。 デフォルトのエンコーディングはプラットフォーム依存で, locale.getpreferredencoding(False) の返す値が使われます。 バイナリデータのファイル入出力ではバイナリモード("rb",'wb"など)を 使うことが推奨されています。また、そうしておけば、python2/python3の双方で想定した通りの 動作をするでしょう。
>>> help(open)
open(...)
open(name[, mode[, buffering]]) -> file object
#デフォルトのエンコーディング
>>> locale.getpreferredencoding(False)
locale.getpreferredencoding(False)
'US-ASCII'
>>> help(open)
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
Open file and return a stream. Raise OSError upon failure.
#デフォルトのエンコーディング
>>> locale.getpreferredencoding(False)
locale.getpreferredencoding(False)
'UTF-8'
4.1.1.1. Python2/Python3の判定¶
python2/python3の双方に対応するプログラムを開発するためには、 今動作中の環境を判定する必要が出てきます。その場合には次のコードを 使うのが良さそうです。
import sys
PY3 = sys.version_info > (3,)
これで, 変数PY3
はpython2では False
,python3では、True
となっています。 sys.version_infoは2.7以降はversion_info型のオブジェクトですが、2.6あるいはそれ以前のバージョンでは、tupleとなっています。
しかし、上記の式はいずれの環境においても正しく動作します。