POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

FeedlyRSSTwitterFacebook
Sebastian Raschka

本記事は、原著者の許諾のもとに翻訳・掲載しております。

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 3 のメリット・デメリット

監修者
監修者_古川陽介
古川陽介
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
複合機メーカー、ゲーム会社を経て、2016年に株式会社リクルートテクノロジーズ(現リクルート)入社。 現在はAPソリューショングループのマネジャーとしてアプリ基盤の改善や運用、各種開発支援ツールの開発、またテックリードとしてエンジニアチームの支援や育成までを担う。 2019年より株式会社ニジボックスを兼務し、室長としてエンジニア育成基盤の設計、技術指南も遂行。 Node.js 日本ユーザーグループの代表を務め、Node学園祭などを主宰。