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