Python3の変更点のメモ

Python3についてはこれまでほとんど目を向けていなかったのだけど、python-devのメーリングリストでPython2系のメンテナンスをどうするかという議論がされているのを見て、改めてPython3について調べてみようと思った。というわけで、Python 3.0のwhat's newを読む。

いろいろ書いてある中で、気になるものをメモ。Python3はapt-get等でインストール後、python3コマンドで起動できる。ここでは、Python 3.2.3で確認した。

$ python3
Python 3.2.3 (default, Feb 27 2014, 21:33:50)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

print文がprint関数に

Python3ではprint文はなくなり、print関数になる。

>>> print("hello world")
hello world
>>> print("hello world", end="")    # 改行しない
hello world>>>
>>> print("foo", "bar", "baz")    # 標準ではスペース (0x20) で区切られる
foo bar baz
>>> print("foo", "bar", "baz", sep="-")    # 区切り文字をハイフンに変更
foo-bar-baz
>>> import sys
>>> print("hello world", file=sys.stderr)    # stderrに出力
hello world

変更の理由はPEP 3105のRationaleの項にある。printを構文として特別扱いする必然性もないので、関数であるべきといったようなところか。

また、Python 3.3ではキーワード引数flushが導入され、Trueを指定することで出力バッファの掃き出しsys.stdout.flush()を同時に行うことができる。これは便利そう。

文字列フォーマット演算子%がstr.formatメソッド

%はdeprecatedになる。けっこうつらい。

>>> "{} {}".format("hello", "world")
'hello world'
>>> "{0} {1} {0}".format("hello", "world")
'hello world hello'
>>> "{x} {y} {x}".format(x="hello", y="world")
'hello world hello'
>>> "{0[x]} {0[y]}".format({"x": "hello", "y": "world"})
'hello world'
>>> import sys
>>> "{0.version}".format(sys)    # attributeの参照
'3.2.3 (default, Jul 23 2012, 16:48:24) \n[GCC 4.5.3]'

変更の理由はPEP 3101にある。タプルかdictのどちらかしか与えられないよりは、関数の引数としてキーワードありなしどちらもいけるほうが柔軟だ、ということらしい。

とはいっても、この見慣れない記法はつらい……。こればかりは慣れるしかないのだろう。

いろんなものがlistの代わりにiteratorを返すようになる

dict.items()range()map()zip()などがlistを返さなくなる。これに合わせて、dict.iteritems()は使えなくなる。

>>> d = dict(a=2, b=3, c=5)
>>> d
{'a': 2, 'c': 5, 'b': 3}
>>> d.items()
dict_items([('a', 2), ('c', 5), ('b', 3)])
>>> d.iteritems()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'iteritems'
>>> map(lambda x: x**x, [2,3,5])
<map object at 0xffe2edcc>
>>> range(5)
range(0, 5)
>>> list(range(5))    # listを得る場合には、明示的に`list()`を用いる
[0, 1, 2, 3, 4]

range()map()でlistを取れなくなるのは少しめんどくさい。[i for i in range(5)]と書くぐらいならlist(range(5))と書くよなあ……。 あとは、dict.iteritems()をついつい使ってしまいそう。

strがunicode文字列になる

unicode()はなくなりstr()に統一される。また、u""リテラルはなくなる(ただしPython 3.3では許されるようになった)。バイト列が必要な場合はb""リテラルを用いることでbytes型として得られる。bytes型はimmutableであるが、bytearray型はmutableである。

また、sys.stdinsys.stdoutsys.stderrがすべてUnicode文字列で読み書きするようになる。 バイト列を読み書きしたい場合はsys.stdout.buffer.write()等とする。

reモジュールによる正規表現処理についても注意が必要である。 Python 3では文字クラスがデフォルトでUnicodeのカテゴリとして扱われるため、\d\wが全角文字などにもマッチする。 ASCII文字のみのマッチにするには、明示的にre.ASCIIフラグをつける、あるいは正規表現の先頭に(?a)をつける必要がある。

>>> import re
>>> re.findall(r'\w', 'aàáâãääāăąǎǟǡǻȁȃȧаӑӓᶏḁẚⱥa𝐚𝑎𝒂𝒶𝓪𝔞𝕒𝖆𝖺𝗮𝘢𝙖𝚊')
['a', 'à', 'á', 'â', 'ã', 'ä', 'ä', 'ā', 'ă', 'ą', 'ǎ', 'ǟ', 'ǡ', 'ǻ', 'ȁ', 'ȃ', 'ȧ', 'а', 'ӑ', 'ӓ', 'ᶏ', 'ḁ', 'ẚ', 'ⱥ', 'a', '𝐚', '𝑎', '𝒂', '𝒶', '𝓪', '𝔞', '𝕒', '𝖆', '𝖺', '𝗮', '𝘢', '𝙖', '𝚊']
>>> re.findall(r'\w', 'aàáâãääāăąǎǟǡǻȁȃȧаӑӓᶏḁẚⱥa𝐚𝑎𝒂𝒶𝓪𝔞𝕒𝖆𝖺𝗮𝘢𝙖𝚊', flags=re.ASCII)
['a']
>>> re.findall(r'(?a)\w', 'aàáâãääāăąǎǟǡǻȁȃȧаӑӓᶏḁẚⱥa𝐚𝑎𝒂𝒶𝓪𝔞𝕒𝖆𝖺𝗮𝘢𝙖𝚊')
['a']
>>> re.findall(r'\d', '01230123')
['0', '1', '2', '3', '0', '1', '2', '3']
>>> re.findall(r'\d', '01230123', flags=re.ASCII)
['0', '1', '2', '3']
>>> re.findall(r'(?a)\d', '01230123')
['0', '1', '2', '3']
>>> re.findall(r'\s', ' \t\n\r\f\v ')
[' ', '\t', '\n', '\r', '\x0c', '\x0b', '\u3000']
>>> re.findall(r'\s', ' \t\n\r\f\v ', flags=re.ASCII)
[' ', '\t', '\n', '\r', '\x0c', '\x0b']
>>> re.findall(r'(?a)\s', ' \t\n\r\f\v ')
[' ', '\t', '\n', '\r', '\x0c', '\x0b']

整数関係の変更

long整数がなくなり、intに統一される。 また、int/intの値がintとならずfloatで返るようになる。小数部分を切り捨ててintで得るためにはint//intとする。

list.sortメソッドに比較関数を使わなくなる

list.sortメソッドが引数cmpを取らなくなる。代わりに比較に用いるキーを返す関数をキーワード引数keyで指定する。また、キーワード引数reverseTrueを指定することで降順でソートされる。

イメージとしては、Perlでいうところのシュワルツ変換を行う感じだろうか。

set、dictを返す内包表記

>>> {i for i in range(0x61, 0x66)}
{97, 98, 99, 100, 101}
>>> {chr(i): i for i in range(0x61, 0x66)}
{'a': 97, 'c': 99, 'b': 98, 'e': 101, 'd': 100}

まあ、いつか役に立つことがあるかもしれない。

モジュール名が整理される

urllib2urllib.requestHTMLParserhtml.parserSimpleHTTPServerhttp.serverなどなど、いろいろと名前が変わる。 これが一番やっかいな気がするけど、とりあえずurllib.requestさえ押さえておけばだいたいは大丈夫な気もする。

>>> import urllib.request as req
>>> f = req.urlopen("http://www.example.com/")
>>> f.readline()
b'<!doctype html>\n'
>>> f.close()