はじめに
もうすぐPythonの最新バージョンのベータ版が公開される予定です。最終の安定版が利用可能になるまでは、もう少し時間がありますが(リリーススケジュールはこちら)、どのような新機能が追加されているのかを調査することには十分価値があります。Python 3.8 では、言語としての構文の追加、既存の挙動に関しての軽微な変更、そして全体的な速度の改善が追加されています。これらの変更は、以前の3.7のリリースまでの習慣を維持したものとなっています。
この記事では Python 3.8 に関して、最も重要な追加点・変更点の概要を説明します。ぜひ見てください。
1. セイウチ演算子
代入式が Python にもセイウチ演算子:=
としてやってきました。これにより、式の一部として変数を代入することができます。後続の条件で式の値を利用したいとき、コードの行数を減らすことができるというメリットがあります。
例えば、このような場合、
line = f.readline()
while line:
... # 何らかの処理
line = f.readline()
以下のように短く書くことが出来ます。
while line := f.readline():
... # 何らかの処理
これはコードを簡潔にしますが、コードの読みやすさに影響を与えると言う人もいるでしょう。最初のコードの方が明確で明示的であると言うこともできます。この議論は、Python コミュニティーにおける論争の的となっていました。(詳細はこちら)
2. 位置引数のみに制限
特別な記号/
を使うことで、この記号の左側の引数を位置引数のみに制限する関数を作ることができるようになりました。Python において、キーワード引数は関数内で*
の記号で利用することができます。また、位置引数のみに制限する追加された/
を使うと、言語の一貫性が増し、堅牢なAPI設計が可能になります。
関数の例を記載します。
def pow(x, y, z=None, /):
r = x**y
if z is not None:
r %= z
return r
/
記号を使うと、x
、y
とz
をキーワード引数としてではなく、位置引数のみを許可するように制限することが出来ます。振る舞いを以下に示します。
>>> pow(2, 10) # 正しい
>>> pow(2, 10, 17) # 正しい
>>> pow(x=2, y=10) # 正しくない、タイプエラーが発生
>>> pow(2, 10, z=17) # 正しくない、タイプエラーが発生
より詳細な説明や、動機、使い方などはこちらを参照してください。
3. f文字列が"="をサポート
Python プログラマーは、デバッグ時に printfスタイルをよく使用します。この書き方は昔はかなり冗長でした。
print "foo=", foo, "bar=", bar
f文字列を使うことで、少し良くなりました。
print(f"foo={foo} bar={bar}")
しかし、まだ文字を繰り返す必要があります。つまり、文字列の"foo"と変数の"foo"の両方を書かなければいけません。
f'{expr=}'
として使われている=
指定子は、式の文字列、等号記号、それから評価された式の表現に展開されます。なので、下記のように簡単に書くことが出来ます。
print(f"{foo=} {bar=}")
言語としては小さな一歩ですが、デバッグ時にprint()
をあちこちにちりばめる人にとっては、大きな一歩ではないかと思います。
4. reversed()
がdict
として動作
Python 3.7 以来、辞書はキーの挿入順序を維持します。組み込みの reversed()
関数を使うと、辞書に逆の順番でアクセスすることができるようになりました。ちょうど、OrderedDict
のような挙動になります。
>>> my_dict = dict(a=1, b=2)
>>> list(reversed(my_dict))
['b', 'a']
>>> list(reversed(my_dict.items()))
[('b', 2), ('a', 1)]
5. return
やyield
に対して反復可能オブジェクトの展開の単純化
Python 3.2 以来、括弧なしで return
や yield
文内で展開をすることができない、という意図しない挙動が存在していました。なので、以下は許可されます。
def foo():
rest = (4, 5, 6)
t = 1, 2, 3, *rest
return t
しかし、以下2つは SyntaxError
となります。
def baz():
rest = (4, 5, 6)
return 1, 2, 3, *rest
def baz():
rest = (4, 5, 6)
yield 1, 2, 3, *rest
最新のリリースではこの挙動が修正されたので、上記の2つの書き方は許容されるようになります。
6. 新しい文法の警告
タプルやリストの前にコンマがないような場合、今回の変更でPythonインタプリタがSyntaxWarning
を発生させるようになりました。しかし以前は、もし誤って以下のようにした場合、
data = [
(1, 2, 3) # おっと、コンマを忘れているよ!
(4, 5, 6)
]
TypeError: 'tuple' object is not callable
というエラーが表示されます。この警告は何が間違っているのかが非常にわかりにくいです。役に立つ警告はコンマが抜けている、ということを指摘することです。これによりデバッグの役に立つでしょう。また、最新のコンパイラは、特定のリテラル型( string や integers など)に対して同一性検査(is
や is not
などを使って)した場合も SyntaxWarning
を発生させます。None
以外のリテラルと比較したいことはほとんどないでしょう。コンパイラの警告によって、わかりにくいバグを回避することができます。
7. パフォーマンス改善
今回のリリースでは、 Python 3.7 のリリースに続いて、いくつかのパフォーマンス改善が追加されています。
operator.itemgetter()
が33%速くなっています。引数処理を最適化し、単一の負でない整数インデックスの一般的なケースの高速パスをタプルに追加することによって可能になりました(これは標準ライブラリの一般的な使用例です)collections.namedtuple()
のフィールド検索が2倍以上速くなりました。これにより、Python におけるもっとも高速なフィールド検索となりました。- 入力された反復可能オブジェクトが既知の長さを持つ場合(つまり len が実装されている)、
list
コンストラクタは内部のアイテムバッファを過剰に割り当てません。これにより、作成されるリストは平均して12%サイズが削減されます。 - クラス変数への書き込みが2倍速くなりました。非dunder属性(double underscore)が更新されるとき、アップデートスロットの不必要な呼び出しが存在していましたが、最適化されました。
- いくつかの組み込みと関数が20-50%速くなりました。引数をこれらの関数に渡す時のオーバーヘッドが削減されます。
uuid.UUID
がメモリフットプリントを削減するために、スロットを使うようになりました。
※dunder属性(double underscore) ... __init__などの特殊属性のこと
まとめ
Python の次のリリースは、言語としての大きな変更や、基礎的な部分に対する重要なパフォーマンス改善が含まれています。Python 3.8 へのアップグレードの中で、既存のコードを変更する必要が出てくる挙動の変更も何点かあります。しかし、パフォーマンス改善や新規構文による全体的なメリットの方が大きいでしょう。全ての新しくなった点の詳細な変更履歴は、ここで見つけることができます。