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.stdin、sys.stdout、sys.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で指定する。また、キーワード引数reverseにTrueを指定することで降順でソートされる。
イメージとしては、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}
まあ、いつか役に立つことがあるかもしれない。
モジュール名が整理される
urllib2→urllib.request、HTMLParser→html.parser、SimpleHTTPServer→http.serverなどなど、いろいろと名前が変わる。
これが一番やっかいな気がするけど、とりあえずurllib.requestさえ押さえておけばだいたいは大丈夫な気もする。
>>> import urllib.request as req
>>> f = req.urlopen("http://www.example.com/")
>>> f.readline()
b'<!doctype html>\n'
>>> f.close()