目次
概要
Python2系から3系で、互換性の無い部分について、特に気をつけなければいけない箇所についてまとめた。
ここでは、versionによる違いを、それぞれ以下の3通りの書き方と実行結果を用いて説明する。
また、2.6以降を利用していることを前提とする。
- 2系での書き方、実行結果
- 3系での書き方、実行結果
- 2系で__future__モジュールを使った書き方、実行結果
__future__モジュールとは
Python2系にデフォルトで入っており、Python3で実装された互換性のない機能をPython2系で利用できるようにするモジュール。つまり、Python2系で、3系の書き方をできるようにするモジュール。
Python2系から3系に移行する際に、一時的に2系でも3系でも動くコードにしたい場合の一時対策として、必要であれば利用するのが望ましい。
__future__はPython2.1で導入された比較的古いモジュールで、Pythonの旧バージョンで新バージョンの挙動を一部先に取り込みたいときに使われてきた。現在ではPython 2系と3系の挙動を近づけるために言及されることが多いが、必ずしも3系対応のために存在するのではない。
2系と3系との違い
- printについて (print_function)
- str型とunicode型について (unicode_literals)
- 割り算の少数点以下の扱いについて (division)
- import方法について (absolute_import)
- その他
1. printについて
Python3系ではprint
は関数になり、予約語からも削除された。
2系
2系での書き方。
print 'version2'
3系
3系での書き方。
# 3系では以下にする
print('version3')
2系で__future__モジュール
Python2系で3系の実装をする場合の書き方。
(printは、2系でもmoduleをimportしなくても3系の書き方が可能だが)
from __future__ import print_function
print("Message")
2. str型とunicode型について
2系では、文字列型はstrとunicodeの2つがあったが、3系ではstrのみとなり、またstrとはunicodeの意味となった。つまり、2系と3系ではstr型の意味が違う。
2系
以下のコードのif文は、strとunicodeを比較することになるので、エラーとなる。
# -*- coding:utf-8 -*-
message = 'サンプル文字列'
print(type(message))
if 'あ' == u'\u3042':
print('True')
実行結果
<type 'str'>
sample.py:5: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
if 'あ' == u'\u3042':
3系
以下のコードはstr(unicode)同士の比較なのでエラーにならない。
また、Python3ではデフォルトのcodingがutf-8になっているため、# -*- coding:utf-8 -*-
を書く必要はない。(あっても問題ない)
message = 'サンプル文字列'
print(type(message))
if 'あ' == u'\u3042':
print('True')
実行結果
<type 'str'>
3系でこれはunicodeの意味。
<type 'str'>
True
よく遭遇するエラー
2系から3系の間では、このエラーに遭遇することがよくある。
AttributeError: 'str' object has no attribute 'decode'"
もしくは
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)
これも、前述のstr型が2系と3系で違うことが原因。
以下のプログラムを2系、3系それぞれで実行してみると、以下のようになる。
# -*- coding:utf-8 -*-
raw = u'サンプル' # python3系では接頭辞uはなくてもunicodeになるので不要。合っても問題はないが。
print(type(raw))
text = raw.encode('utf-8')
print(type(text))
decoded = text.decode('utf-8')
print(type(decoded))
2系の結果
<type 'unicode'>
<type 'str'>
<type 'unicode'>
3系の結果
<type 'str'>
<type 'bytes'>
<type 'str'>
2系ではstr型をencodeするとunicode型になるが、3系でstr型をencodeするとbytes型になる。
エラーが発生する原因
3系でstrをdecodeしようとすると、以下のエラーが出る。encodeは可能。
AttributeError: 'str' object has no attribute 'decode'"
2系でstrをencodeしようとすると、以下のエラーが出る。decodeは可能。
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)
つまり、str型をdecodeする記述のあるプログラムは、2系では動くが3系では動かない。また逆に、str型をencodeする記述は、3系では動くが2系では動かない。それぞれ環境に合わせて修正する必要がある。
2系で__future__モジュール
Python2系で3系の実装をする場合。
以下のコードはunicode同士の比較なのでエラーにならない。
unicode_literalsモジュールは、prefix無しの文字列リテラルをunicodeにする。
以下のコードの'あ'
は、u'あ'
と同義になる。
また、こちらでは# -*- coding:utf-8 -*-
が必要。
# -*- coding:utf-8 -*-
from __future__ import unicode_literals
message = 'サンプル文字列'
print(type(message))
if 'あ' == u'\u3042':
print('True')
実行結果
2系で実行すると<type 'unicode'>
となる。
3系で実行すると<type 'str'>
となる。
<type 'unicode'>
True
3. 割り算の少数点以下の扱いについて
2系では、int同士の割り算は小数点以下切り捨てだったが、3系ではfloatになる。
3系で切り捨てにしたい場合は、演算子「//」 を使う。
2系
print(1/2)
print(1//2)
実行結果
0
0
3系
print(1/2)
print(1//2)
実行結果
0.5
0
2系で__future__モジュール
Python2系で3系の実装をする場合。
from __future__ import division
print(1/2)
print(1//2)
実行結果
0.5
0
4. import方法について
2系と3系ではimportの方法に違いがある。
2系では、カレントディレクトリのモジュールが優先されるが、3系では相対より絶対インポートが優先となる。これは、違うディレクトリに同名のファイルがある場合に問題となり得る。
実際に例を見たほうが早いので、以下に例を挙げる。
ディレクトリ構成
以下の構成でPythonスクリプトを作成する。
greeting.pyとlibs/greeting.pyという同名の2つのファイルがある。
libs/greeting.pyをlibs/sample_lib.pyからimport greeting
として読み込んだときに、2系ではlibs/greeting.pyがimportされるが、3系では一番上の階層のgreeting.pyがimportされる。
先述したとおり、3系のimportでは相対パスより絶対パスが優先されるためだ。
.
|-- greeting.py
`-- libs
|-- __init__.py
|-- greeting.py
`-- sample_lib.py
スクリプトの中身
greeting.py
from libs import sample_lib
libs/__init__.py
空で作成する。
libs/greeting.py
def hello():
return 'Hello'
libs/sample_lib.py
import greeting
print(greeting.hello())
2系
top階層のgreeting.pyを実行する。
libs/sample_lib.pyでlibs/greeting.pyがimportされる。
$ python greeting.py
Hello
3系
一番上の階層のgreeting.pyを実行する。
3系では、libs/sample_lib.pyで一番上の階層のgreeting.pyをimportしようとし、hello()
メソッドがないためエラーとなる。
$ python greeting.py
Traceback (most recent call last):
File "greeting.py", line 1, in <module>
from libs import sample_lib
File "/home/username/libs/sample_lib.py", line 3, in <module>
print(greeting.hello())
AttributeError: module 'greeting' has no attribute 'hello'
修正方法
libs/sample_lib.pyを以下の用に修正する。
-import greeting
+from . import greeting
print(greeting.hello())
これで3系でエラーが出なくなる。
$ python greeting.py
Hello
2系で__future__モジュール
libs/sample_lib.pyでabsolute_importをimportし、以下のように修正する。
(printを使っているのでprint_functionもimportする)
-import greeting
+from __future__ import print_function
+from __future__ import absolute_import
+from . import greeting
print(greeting.hello())
これでエラーが出なくなる。
$ python greeting.py
Hello
その他
以下は、2to3やfutureで検出されない2系と3系の違い。
tempfile
2系と3系では、tempfileモジュールのdefaultの書き込みモードが違う。
ver. | mode |
---|---|
2 | テキスト |
3 | バイナリ |
Python3系で、2系同様にテキストを書き込もうとすると、以下のエラーが出る。
TypeError: a bytes-like object is required, not 'str'
3系でテキストモードで書き込む場合は、以下の様にオプションを指定する必要がある。
# -*- coding:utf-8 -*-
import tempfile
with tempfile.NamedTemporaryFile(mode='w+t', encoding='utf-8', delete=False) as tf:
tf.write('テキスト')
参考:python3 の tempfile + write はデフォルトのままだと .write() が求められる
byte型とstring型
以下は2系のソースを3系になおすときによく使用した。
参考:Python 3 での文字列とバイト列の相互変換と16進数表示
# 文字列をbyte型に変換
'abcd'.encode()
# byte型を文字列に変換
b'abcd'.decode()