LoginSignup
4

More than 3 years have passed since last update.

【Python2to3アップデート方法】Python2系と3系の違い

Last updated at Posted at 2019-04-16

目次

概要

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系との違い

  1. printについて (print_function)
  2. str型とunicode型について (unicode_literals)
    1. よく遭遇するエラー
  3. 割り算の少数点以下の扱いについて (division)
  4. import方法について (absolute_import)
  5. その他

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

greeting.py
from libs import sample_lib

libs/__init__.py

空で作成する。

libs/greeting.py

greeting.py
def hello():
    return 'Hello'

libs/sample_lib.py

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を以下の用に修正する。

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する)

libs/sample_lib.py
-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()

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4