2014年8月14日
Python 2.7.x と 3.x の決定的な違いを例とともに
本記事は、原著者の許諾のもとに翻訳・掲載しております。
Pythonを始めたばかりのユーザーの多くが、どちらのバージョンを使えばいいのか迷っています。私の答えは、「気に入ったチュートリアルに書かれているバージョンにしましょう。そして、あとで違いを調べてください」という言葉につきます。 それでは、新しいプロジェクトを始めるときにはどちらを選べばいいのでしょうか? 使おうとしているライブラリを全てサポートしているなら、2.7.x系と3.x系のどちらを使ってもよいでしょう。そうはいっても、この2つのメジャーバージョンについて大きな違いを見ておくのは良いでしょう。どちらかのみでコードを書いたり、プロジェクトに使おうとしている時によくある落とし穴を避けられるからです。
__future__
モジュール
Python 3.x で導入されていて Python 2 で使えないキーワードについては、 __furute__
モジュールをインポートすることで Python 2 で使えます。あなたのコード中で Python 3 をサポートするときは __future__
モジュールのインポートが推奨されています。例えば Python 3.x の整数除算のの挙動を Python 2 に取り入れたいときは、以下のようにインポートします。
from __future__ import division
他にも以下の表にある機能がインポートできます
feature | optional in | mandatory in | effect |
---|---|---|---|
nested_scopes | 2.1.0b1 | 2.2 | PEP 227 : Statically Nested Scopes |
generators | 2.2.0a1 | 2.3 | PEP 255 : Simple Generators |
division | 2.2.0a2 | 3.0 | PEP 238 : Changing the Division Operator |
absolute_import | 2.5.0a1 | 3.0 | PEP 328 : Imports: Multi-Line and Absolute/Relative |
with_statement | 2.5.0a1 | 2.6 | PEP 343 : The “with” Statement |
print_function | 2.6.0a2 | 3.0 | PEP 3105 : Make print a function |
unicode_literals | 2.6.0a2 | 3.0 | PEP 3112 : Bytes literals in Python 3000 |
(ソース: https://docs.python.org/2/library/future.html)
from platform import python_version
print 関数
些細な事かもしれませんが、print構文が変わったことは広く知られている変化ですが、取り上げる価値があります。Python 2のprint文は print()
関数で置き換えられます。printしたいオブジェクトを丸括弧で囲うことが必要になります。
Python 2 では括弧が追加されても問題ありませんが、Python 3 では対称的に Python 2 のやり方で括弧のないprint関数は SyntaxError
になります。
Python 2
print 'Python', python_version()
print 'Hello, World!'
print('Hello, World!')
print "text", ; print 'print more text on the same line'
Python 2.7.6
Hello, World!
Hello, World!
text print more text on the same line
Python 3
print('Python', python_version())
print('Hello, World!')
print("some text,", end="")
print(' print more text on the same line')
Python 3.4.1
Hello, World!
some text, print more text on the same line
print 'Hello, World!'
File "", line 1
print 'Hello, World!'
^
SyntaxError: invalid syntax
注釈
Python2で “Hello, World” を上記のように印字するのは極めてふつうのことです。しかし丸括弧のなかに複数のオブジェクトがあった時は、 print
は文なので、タプルを作ることになります。
print 'Python', python_version()
print('a', 'b')
print 'a', 'b'
Python 2.7.6
('a', 'b')
a b
整数除算
コードを移植しようとしたり、Python3のコードをPython2で実行しているなら、除算の変化はとりわけ危険になります。整数除算の挙動の変化は、しばしば気づかれないからです。( SyntaxError
は発生しません)なのでPython3のスクリプトではPython2を使ってる人の面倒ごと省くために、 3/2
と書かずに float(3)/2
もしくは 3/2.0
と書くようにしています(逆もまたりかりで、Python2のスクリプトでは from __future__ import division
を使うように推奨しています)。
Python 2
print 'Python', python_version()
print '3 / 2 =', 3 / 2
print '3 // 2 =', 3 // 2
print '3 / 2.0 =', 3 / 2.0
print '3 // 2.0 =', 3 // 2.0
Python 2.7.6
3 / 2 = 1
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0
Python 3
print('Python', python_version())
print('3 / 2 =', 3 / 2)
print('3 // 2 =', 3 // 2)
print('3 / 2.0 =', 3 / 2.0)
print('3 // 2.0 =', 3 // 2.0)
Python 3.4.1
3 / 2 = 1.5
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0
Unicode
Python 2 では ASCIIの str()
型を、 unicode()
とは別に持っていますが、 byte
型はありません。Python 3 では、ついに Unicode (utf-8) の str
文字列と、 2バイトクラス: byte
と bytearray
が登場しました。
Python 2
print 'Python', python_version()
Python 2.7.6
print type(unicode('this is like a python3 str type'))
<type 'unicode'>
print type(b'byte type does not exist')
<type 'str'>
print 'they are really' + b' the same'
they are really the same
print type(bytearray(b'bytearray oddly does exist though'))
<type 'bytearray'>
Python 3
print('Python', python_version())
print('strings are now utf-8 \u03BCnico\u0394é!')
Python 3.4.1
strings are now utf-8 μnicoΔé!
print('Python', python_version(), end="")
print(' has', type(b' bytes for storing data'))
Python 3.4.1 has <class 'bytes'>
print('and Python', python_version(), end="")
print(' also has', type(bytearray(b'bytearrays')))
and Python 3.4.1 also has <class 'bytearray'>
'note that we cannot add a string' + b'bytes for data'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 'note that we cannot add a string' + b'bytes for data'
TypeError: Can't convert 'bytes' object to str implicitly
xrange
xrange()
は Python 2.x ではイテラブルなオブジェクトを作るのによく使われています。例えば、for-loop や list/setとディクショナリの内包表記などがあります。挙動はジェネレータ(例: 遅延評価)とよく似ています。しかしxrangeのイテラブルは枯渇することはありません。無限にイテレート可能なのです。
遅延評価のおかげで、 range()
の xrange()
に対する利点は、一回で全てイテレートする場合に若干速いということにとどまります。(for-loopなど)しかし、一度のイテレーションとは対照的に、何度もイテレーションしてしまうときには推奨されません。なぜなら、いつも一からジェネレートが起こってしまうからです。
Python 3 では range()
は xrange()
関数のように実装されています。おかげで専用の xrange()
はもうありません。
import timeit
n = 10000
def test_range(n):
for i in range(n):
pass
def test_xrange(n):
for i in xrange(n):
pass
Python 2
print 'Python', python_version()
print '\ntiming range()'
%timeit test_range(n)
print '\n\ntiming xrange()'
%timeit test_xrange(n)
Python 2.7.6
timing range()
1000 loops, best of 3: 433 µs per loop
timing xrange()
1000 loops, best of 3: 350 µs per loop
Python 3
print('Python', python_version())
print('\ntiming range()')
%timeit test_range(n)
Python 3.4.1
timing range()
1000 loops, best of 3: 520 µs per loop
print(xrange(10))
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in ()
----> 1 print(xrange(10))
NameError: name 'xrange' is not defined
range オブジェクトの __contains__
メソッド
他にも、Python 3.xでは range
が 新たに __contains__
メソッドを手に入れました。( Yuchen Ying さん、ご指摘ありがとうございます) __contains__
メソッドは Python 3.x の range
における 整数とブール値型のルックアップを劇的に高速化します。
x = 10000000
def val_in_range(x, val):
return val in range(x)
def val_in_xrange(x, val):
return val in xrange(x)
print('Python', python_version())
assert(val_in_range(x, x/2) == True)
assert(val_in_range(x, x//2) == True)
%timeit val_in_range(x, x/2)
%timeit val_in_range(x, x//2)
Python 3.4.1
1 loops, best of 3: 742 ms per loop
1000000 loops, best of 3: 1.19 µs per loop
timeit
の結果をみれば、 浮動小数点数に比べて整数ルックアップの実行速度が60,000倍も速いことがわかります。しかし Python 2.x の range
や xrange
は __contains__
メソッドを持っていないので、整数と浮動小数点数のルックアップはそこまで変わりません。
print 'Python', python_version()
assert(val_in_xrange(x, x/2.0) == True)
assert(val_in_xrange(x, x/2) == True)
assert(val_in_range(x, x/2) == True)
assert(val_in_range(x, x//2) == True)
%timeit val_in_xrange(x, x/2.0)
%timeit val_in_xrange(x, x/2)
%timeit val_in_range(x, x/2.0)
%timeit val_in_range(x, x/2)
Python 2.7.7
1 loops, best of 3: 285 ms per loop
1 loops, best of 3: 179 ms per loop
1 loops, best of 3: 658 ms per loop
1 loops, best of 3: 556 ms per loop
以下は Python 2.x では __contain__
メソッドが実装されてないことの証明です。
print('Python', python_version())
range.__contains__
Python 3.4.1
<slot wrapper '__contains__' of 'range' objects>
print 'Python', python_version()
range.__contains__
Python 2.7.7
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
1 print 'Python', python_version()
----> 2 range.__contains__
AttributeError: 'builtin_function_or_method' object has no attribute '__contains__'
print 'Python', python_version()
xrange.__contains__
Python 2.7.7
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
1 print 'Python', python_version()
----> 2 xrange.__contains__
AttributeError: type object 'xrange' has no attribute '__contains__'
Python 2 と 3 の速度の違いについてのメモ
Python 3 の range()
と Python 2 の xrange()
の速度の差について指摘する人もいます。同じように実装されているのだから同じ速さになるだろうからです。しかし、以下の違いは Python 3 は 一般に Python 2 より遅くなるという事実からきています。
def test_while():
i = 0
while i < 20000:
i += 1
return
print('Python', python_version())
%timeit test_while()
Python 3.4.1
100 loops, best of 3: 2.68 ms per loop
print 'Python', python_version()
%timeit test_while()
Python 2.7.6
1000 loops, best of 3: 1.72 ms per loop
例外送出
Python 2 が新旧どちらの構文もうけつけるようなところでは、丸括弧で例外を閉じなければ、 Python 3 は( SyntaxError
を出して)行き詰まってしまいます。
Python 2
print 'Python', python_version()
Python 2.7.6
raise IOError, "file error"
---------------------------------------------------------------------------
IOError Traceback (most recent call last)
in ()
----> 1 raise IOError, "file error"
IOError: file error
raise IOError("file error")
---------------------------------------------------------------------------
IOError Traceback (most recent call last)
in ()
----> 1 raise IOError("file error")
IOError: file error
Python 3
print('Python', python_version())
Python 3.4.1
raise IOError, "file error"
File "", line 1
raise IOError, "file error"
^
SyntaxError: invalid syntax
print('Python', python_version())
raise IOError("file error")
Python 3.4.1
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
in ()
1 print('Python', python_version())
----> 2 raise IOError("file error")
OSError: file error
例外処理
Python 3 では例外処理もすこし変わりました。Python 3 では as
キーワードを使う必要があります。
Python 2
print 'Python', python_version()
try:
let_us_cause_a_NameError
except NameError, err:
print err, '--> our error message'
Python 2.7.6
name 'let_us_cause_a_NameError' is not defined --> our error message
Python 3
print('Python', python_version())
try:
let_us_cause_a_NameError
except NameError as err:
print(err, '--> our error message')
Python 3.4.1
name 'let_us_cause_a_NameError' is not defined --> our error message
next() 関数 と .next() メソッド
next()
( .next()
) はよく使われる関数ですが、以下は言及する価値のある構文の変化(そして実装の変化)です。Python 2.7.5 で関数とメソッドの構文を使えるところでは、Python 3 では next()
関数しか残っていません。( .next()
メソッドを呼ぶと AttributeError
になります)
Python 2
print 'Python', python_version()
my_generator = (letter for letter in 'abcdefg')
next(my_generator)
my_generator.next()
Python 2.7.6
'b'
Python 3
print('Python', python_version())
my_generator = (letter for letter in 'abcdefg')
next(my_generator)
Python 3.4.1
'a'
my_generator.next()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
----> 1 my_generator.next()
AttributeError: 'generator' object has no attribute 'next'
For-loop変数とグローバル名前空間への漏れ
朗報: Pthon 3.x の for-loop 変数はグローバル名前空間へ漏れることがなくなりました!
What’s New In Python 3.0 で取り上げた変化にさかのぼりますが、以下のようになります:
リスト内包表記は次の形式はサポートしません
[... for var in item1, item2, ...]
代わりに[... for var in (item1, item2, ...)]
を使ってください。また、リスト内包表記は違った意味になります:list()
コンストラクタ内部のジェネレータ文に近い糖衣構文になります。特に、ループ制御変数はその周りのスコープに漏出しません。
Python 2
print 'Python', python_version()
i = 1
print 'before: i =', i
print 'comprehension: ', [i for i in range(5)]
print 'after: i =', i
Python 2.7.6
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 4
Python 3
print('Python', python_version())
i = 1
print('before: i =', i)
print('comprehension:', [i for i in range(5)])
print('after: i =', i)
Python 3.4.1
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 1
列挙できない型の比較
他にも Python 3 でのナイスな変化といえば、列挙不可能な型同士を比較しようとしたときに、 TypeError
が警告として送出されるようになったことがあります。
Python 2
print 'Python', python_version()
print "[1, 2] > 'foo' = ", [1, 2] > 'foo'
print "(1, 2) > 'foo' = ", (1, 2) > 'foo'
print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2)
Python 2.7.6
[1, 2] > 'foo' = False
(1, 2) > 'foo' = True
[1, 2] > (1, 2) = False
Python 3
print('Python', python_version())
print("[1, 2] > 'foo' = ", [1, 2] > 'foo')
print("(1, 2) > 'foo' = ", (1, 2) > 'foo')
print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))
Python 3.4.1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
1 print('Python', python_version())
----> 2 print("[1, 2] > 'foo' = ", [1, 2] > 'foo')
3 print("(1, 2) > 'foo' = ", (1, 2) > 'foo')
4 print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))
TypeError: unorderable types: list() > str()
input() でのユーザー入力の構文解析
ついてることに、 input()
関数は Python 3 でユーザー入力を常に str
オブジェクトとして保持するように直されました。Python 2 においては、 strings
型以外の型として読みこむような危険な振る舞いを避けるため、 raw_input()
を使わなければなりません。
Python 2
Python 2.7.6
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> my_input = input('enter a number: ')
enter a number: 123
>>> type(my_input)
<type 'int'>
>>> my_input = raw_input('enter a number: ')
enter a number: 123
type(my_input)
<type 'str'>
Python 3
Python 3.4.1
[GCC 4.2.1 (Apple Inc. build 5577)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> my_input = input('enter a number: ')
enter a number: 123
>>> type(my_input)
<class 'str'>
リストを使わずにイテラブルなオブジェクトを返す
xrange
の節で見たように、Python 3 のある関数やメソッドでは Python 2 ではリストとして返していたものを、イテラブルなオブジェクトとして返します。
私達はよく一度にイテレートするので、この変化のおかげでかなりメモリーを省力できると考えていました。しかし、ジェネレータとは対照的に、必要であれば何度でも全体をイテレートすることができます。ただしそこまで効率的ではないのですが。
本当にリストオブジェクトが必要な場合、 list()
関数を使って list
に変換することができます。
Python 2
print 'Python', python_version()
print range(3)
print type(range(3))
Python 2.7.6
[0, 1, 2]
<type 'list'>
Python 3
print('Python', python_version())
print(range(3))
print(type(range(3)))
print(list(range(3)))
Python 3.4.1
range(0, 3)
<class 'range'>
[0, 1, 2]
以下のよく使われている関数とメソッドは、Python3ではもうリストを返さなくなっています:
zip()
map()
filter()
- ディクショナリの
.keys()
メソッド - ディクショナリの
.values()
メソッド - ディクショナリの
.items()
メソッド
Python 2 と Python 3 についての記事
フォローアップにおすすめな、Python 2 と 3 をつなぐ良質な記事のリストをあげておきます。
**// Python 3 への変換 **
- Python 2 と Python 3 のどちらを開発に使うべき?
- Python 3.0 で新しくなったこと
- Python 3 に接続する
- Python 2 コードを Python 3 に接続する
- Python 3 で前進し続ける方法
// Python 3 のメリット・デメリット
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa