methaneのブログ

このブログに乗せているサンプルコードはすべてNYSLです。

Python 3.6 の(個人的に)注目の変更点

Python 3.6b1 がリリースされましたね。(フライング)

beta1 ということで、 3.6 に向けた新機能の追加は (provisional package を除いて) 終了です。ただし、仕様が確定したと言うわけではなくて、beta版に対するフィードバックを元に新機能を修正したり、最悪 revert して 3.7 に持ち越しにされる可能性もあります。

なお、 3.6b1 が出る前の1週間が core dev sprint があり、そこでめちゃくちゃ大量に大きめの変更が入りました。なので、常用環境には全くオススメできませんが、OSS開発者だったら .travis.ymlpython: "nightly" を追加してリグレッションの発見に貢献したり(←これめっちゃ有り難いです)、それ以外の人も 3.6 を試してみて早めにフィードバックをしてもらえると、年末の 3.6 がより完成度が高いもになると思います。

では行きましょう

dict が省メモリかつ挿入順を保持するようになった

僕がパッチを投げていた、 CPython の dict を PyPy の dict と同じような仕組みにするパッチが取り込まれました。 php, Ruby につづいて Python もデフォルトで dict が挿入順を維持するようになりました。

とはいえ、パッチが取り込まれたのが金曜日で、まだどこまでが言語仕様でどこからが CPython の実装詳細なのかは微妙なところです。 今のところの Guido の宣言では、 dict の順序は実装依存で、キーワード引数や名前空間__dict__ は言語仕様として挿入順を維持する (他の Python 3.6 言語実装では dict ではなく OrderedDict などのサブクラスを返すかもしれない) ということになりそうです。

しかし、「そんなことしたら仕様確認せずに動作だけ見て思い込みで実装依存なコードが増えるじゃないか!」「いやいや、 CPython と PyPy がメジャーな実装で、その2つで動けばいいって判断したならそれに頼るのはOKだろ」「micropython はもともと性能(衝突頻度)よりも省メモリ重視でハッシュテーブルをそんなにスカスカにしてないからこの実装に追従してもコンパクトにならないんだけど」などといろんな意見が飛び出していて予断を許しません。

個人的には Guido の方針に賛成です。特に IronPythonJython などの GIL がない実装が Python 3.6 に追いついたときに、マルチスレッドで性能が出やすい実装を選ぶ余地を残しておいて欲しいからです。

ということで、みなさんは「順序を維持してくれたほうが(ログが見やすくなったりとかで)使い勝手いいけど、必須ではない」というところだけ dict の挿入順維持に頼ってください。

なお、 OrderedDict が新しい dict の実装を利用してより省メモリにできるはずですが、 dict の新実装が取り込まれたのが金曜日なので、月曜日に間に合わせるのは妻子持ちには無理だと最初から諦めてましたごめんなさい。

f-string

"{foo} and {bar}".format(foo=foo, bar=bar) を、 f"{foo} and {bar}" と書けるようになりました。 {} の中にはちゃんと式が書けます。やったね! phpRuby に追いついたね!

なお、 f-string 内でのエスケープシーケンスの使い方については制限があります。詳しくは言語仕様 を参照してください。

pathlib が使いやすくなった

pathlib.Path 経由で直接ファイルを読み書きするときは良かったのですが、他のライブラリとかにパスを渡すときは、今までは渡すときに文字列に変換してあげないといけないことが多くてあまり使い勝手が良くありませんでした。

Python 3.6 では os のいろんな関数が pathlib.Path をサポートするようになったので、そのライブラリが引数をそのまま os の関数に渡しているようなときには、文字列への変換が不要になりました。ダックタイピング万歳!

ただし、ライブラリが引数に対して isinstance(file, str) とかして、引数が文字列ならファイルを開いてファイルオブジェクトなら直接使うみたいな分岐をしている場合は、そのライブラリの対応を待たないといけません。

Windows でファイル名やコンソール入出力が utf-8

Pythonは基本的に unicode 推しで、特に Windowsバイト列でファイル名を利用すると Warning を出しつつ、内部では Windows*AAPI を利用していました。

しかし、 Mercurial などのように生まれも育ちも設計思想も unix な、ファイル名をバイト列として扱うツールを Windows で動かすときに、今さらレガシーなエンコーディング使うのは嫌だという話があり、ファイル名とコンソール入出力をバイト列で行ったときは utf-8 <> utf-16 変換を行って W 系 API を呼ぶようになりました。

これで後方互換性に問題がでるアプリのために、環境変数 PYTHONLEGACYWINDOWSSTDIO, PYTHONLEGACYWINDOWSFSENCODING で元の挙動に戻すこともできます。

あと、 Windows 10 Anniversary Update で MAX_PATH の 260 文字制限を超えられるようになりましたが、 Python も問題のある Windows API を使ってなかったので、これに対応するように Manifest が書かれました。 260文字超えたパスを扱えます。

C99 解禁

Python プログラマーには全く関係のない話ですが、 Python を実装するときのC言語の仕様で C99 の幾つかの機能がやーーーっと解禁されました。 cflags にも -std=c99 が追加されます。

例えば、1行コメントが // で書けるようになったとか、 Py_LOCAL_INLINE(void) foo(void)static inline void foo(void) になったりとか、 int16_t が使えるようになったりとか、ローカル変数が関数やブロックの先頭以外の(初期化する位置で)宣言できるようになったりとか、etcetc、新しい Python ハッカーにとっては大幅にリーダビリティが上がったと思います。君も Python ハッカーにならないか?

なお、早速 Python のバグトラッカーに、 CentOS 5 のデフォルトの GCC でビルドが通らない!って報告が来てます。とても Python らしいですね。新しい gcc インストールしてください。というか CentOS 5 とか RHEL 5 とか捨ててください。

高速化

PythonからCの関数を呼び出す時、今まではVMのスタックから引数を取り出してタプルに入れてC関数に渡していました。 それが新しく加わった呼び出し規約では、VMスタックから直接引数を取り出せるようになり、一時オブジェクトとしてのタプルが不要になりました。

この呼び出し規約は人間が直接書くことはあまり意図されていず、 Argument Clinic という、シグネチャを決められたシンタックスで書けば引数解析コードを自動生成してくれるツールを使って対応することになります。 CPython の多くのビルトイン関数はすでにこのツールを使っているので、いろんなCで書かれた関数の呼び出しが速くなったはずです。

また、バイトコードのフォーマットが大幅にかわり、1つの命令が2バイト使うようになりました。(新しいフォーマットを区別するときはワードコードと呼ばれてます。) これにより Python 3.6 でも少し高速化されてるはずですが、本格的に新しいフォーマットの力を引き出すような高速化は 3.7 に持ち越しです。

他にも将来の高速化へ向けた仕込みとして、 dict に(CPython内部からしか使えないプライベートな)バージョン番号が入って名前解決結果をバージョンが変わるまでキャッシュできるようになったり、バイトコードを格納しているコードオブジェクトに co_extra というフィールドが追加されてサードパーティーのプロファイラーやJITエンジンが利用できるようにしたりしてます。

Ruby 3x3 が話題ですが、 Python の高速化からも目が離せません。