「Python 3.9」登場、追加された辞書の和集合演算子、removeprefix/removesuffixメソッドとは:Python最新情報キャッチアップ
Python 3.9の新機能や変更点の中から、辞書に対する和集合演算子の追加、文字列からプリフィックスとサフィックスを削除するメソッドの追加などを紹介する。
2020年10月5日にPythonの最新バージョンであるPython 3.9がリリースされた。そこで、今回と次回の2回に分けて、Python 3.9で追加、変更された機能から幾つかをピックアップしてお知らせしていこう。
概要
Python 3.9の新機能については「What's New In Python 3.9」にまとめられているので、そちらを参照されたい。大きな機能追加や変更点としては次のものが挙げられる。
- 辞書に対する和集合演算子の追加
- 文字列から指定したプリフィックスとサフィックスを削除するメソッドの追加
- 標準コレクション型を型ヒントとして使用できるように
- デコレーター記述の制約を緩く
- 新しいパーサーの採用
今回はこれらのうちの幾つかについて見ていこう。ただ、その前に簡単にPython 2.7のサポート終了にまつわる変更点についても取り上げておく。
Python 2.7のサポートが終了したことで、Python 3からはPython 2に対する後方互換性レイヤーが取り除かれたか、近いうちに取り除かれる。これらのレイヤーにより、これまではDeprecationWarningが表示されていたが、このような後方互換性レイヤーを提供するのはPython 3.9が最後ということだ。DeprecationWarningを確認するには、pythonコマンドに「-W default」か「-W error」を付加して、Pythonコードを実行する。前者はDeprecationWarningまたはPendingDeprecationWarningを表示し、後者はエラーを発生させる。この機能を使って、早めにPython 3への移行に取りかかった方がよいだろう。
辞書に対する和集合演算子の追加
Python 3.9では和集合演算子「|」を使って、2つの辞書の要素を含んだ新しい辞書を作成できるようになった。辞書を「インプレース」で更新するにはupdateメソッドを使えたが、Python 3.9ではこれについても累算代入演算子の「|=」を使えるようになった。
2つの辞書の要素を1つにまとめる、という意味では今も述べたupdateメソッドが使える。ただし、updateメソッドはあくまでもインプレースでこれを行う。
d1 = {'k1': 'v1', 'k2': 'v2'}
d2 = {'k3': 'v3', 'k4': 'v4'}
d1.update(d2)
print(d1) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
そうではなく、元の辞書には影響を及ぼすことなく、2つの辞書の要素をマージした「新しい」辞書が必要だという場合に新しい演算子が役に立つ。この演算子がない環境(つまり、Python 3.8以前)では、同じことをするには次のようなコードを書くことになる。
d1 = {'k1': 'v1', 'k2': 'v2'}
d2 = {'k3': 'v3', 'k4': 'v4'}
# 一方の辞書のコピーを作成して、もう一方の辞書の内容を使ってそれを更新
d3 = d1.copy()
d3.update(d2)
print(d3) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
# 組み込みのdict(mapping, **kwarg)関数を使う
d4 = dict(d1, **d2)
print(d4) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
# 辞書リテラルの要素としてd1、d2の要素を使用
d5 = {**d1, **d2}
print(d5) # print(d3) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
直観的なものもあれば、そうでないものもあり、さらには一見すると何をしているか分からないものもある(最後の例)。これに対して、Python 3.9では次のようにシンプルに同じことを書ける。
# Python 3.9で有効
d1 = {'k1': 'v1', 'k2': 'v2'}
d2 = {'k3': 'v3', 'k4': 'v4'}
d3 = d1 | d2 # Python 3.8以前ではTypeError
print(d3) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
また、updateメソッドを使わずに、次のように累算代入演算子の「|=」を使って、辞書の更新を行えるようにもなった。
# Python 3.9で有効
d1 = {'k1': 'v1', 'k2': 'v2'}
d2 = {'k3': 'v3', 'k4': 'v4'}
d1 |= d2 # Python 3.8以前ではTypeError
print(d1) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
「|=」演算子では、右項の被演算子には辞書以外の反復可能オブジェクトも指定できる。ただし、辞書以外の反復可能オブジェクトでは、その要素が2つの要素を持つ必要がある。例えば、「2要素からなるタプルを格納するリスト」などがそうだ。これはdict関数や辞書のupdateメソッドに引数として指定可能な反復可能オブジェクトの形式と同様だ。以下に例を示す。
d1 = {'k1': 'v1', 'k2': 'v2'}
l = [('k3', 'v3'), ('k4', 'v4')]
d1 |= l # Python 3.8以前ではTypeError
print(d1) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
d1 | l # TypeError
なお、上のコード例の最後に示したように、「|」演算子では辞書同士のマージしか許されていないことには注意しよう。
今見たように和集合演算子「|」では辞書同士の演算のみがサポートされる一方で、累算代入演算子「|=」(updateメソッド)では他のオブジェクトを被演算子(引数)に指定できるというのは、リストにおける連結演算子「+」ではリスト同士の演算のみがサポートされ、累算代入演算子「+=」(extendメソッド)では他の反復可能オブジェクトを指定可能であることと似た関係にある。以下にリストとタプルで連結、加算代入を行う例を示すので、上のコードと見比べてほしい。
# リストとタプル
l = [0, 1, 2]
t = (3, 4, 5)
l += t # [0, 1, 2, 3, 4, 5]:リストにタプルを加算代入することは可能
l + t # TypeError:リストとタプルの連結はできない
最後に、キーが重複した場合についても話しておく。といっても、右側にあるキー/値の組により、以前のキーの値が上書きされるというだけだ。
d1 = {'k1': 'v1', 'k2': 'v2'}
d2 = {'k2': 'foo', 'k3': 'v3'}
print(d1 | d2) # {'k1': 'v1', 'k2': 'foo', 'k3': 'v3'}
print(d2 | d1) # {'k2': 'v2', 'k3': 'v3', 'k1': 'v1'}
このコードでは変数d1とd2の順番を変えて、「|」演算子を適用している。1つ目の例では、キー「'k2'」の値が右側のd2に含まれている値となっていること、2つ目の例ではその逆になっていることに注目してほしい。
文字列から指定したプリフィックスとサフィックスを削除するメソッドの追加
Python 3.9では文字列(strおよびbytes、bytearrayなど)に対して、その先頭または末尾から特定のプリフィックスまたはサフィックスを取り除くremoveprefixメソッドとremovesuffixメソッドが追加された。
簡単な例を以下に示す。
filename = 'test_document.txt'
print(filename.removeprefix('test_')) # document.txt
print(filename.removesuffix('.txt')) # test_document
print(filename.removeprefix('sample_')) # test_document.txt
print(filename.removesuffix('.doc')) # test_document.txt
見れば分かる通り、removeprefixメソッドは引数に指定したプリフィックスで文字列が始まっていれば、それを取り除いた新しい文字列を返送し、そうでなければ元の文字列を返送する。removesuffixメソッドはその末尾版だ。
似た機能を持つメソッドとしては、stripメソッド/lstripメソッド/rstripメソッドがあるが、これらがパラメーターに受け取るのは「削除したい文字の集合」である点に注意しよう。以下にrstripメソッドの使用例を示す。
filename = 'test_document.txt'
print(filename.rstrip('.txt')) # test_documen
上のコードは「test_document.txt」から末尾の「.txt」を取り除くことを意図したものだが、これを実行すると「test_documen」と1文字余計に取り除かれてしまう。これはrstripメソッドに渡した「.txt」があくまでも「削除したい文字の集合」であり、文字列の末尾から「.」「t」「x」「t」ではない文字に当たるまではマッチしたものが削除されてしまうからだ。その結果、「.」の左側にある「document」の「t」まで削除されてしまう。
このように、文字列が特定の文字列で始まっていれば(または、特定の文字列で終わっていれば)それを取り除くという処理を行うのに、strip/lstrip/rstripメソッドは都合が悪い。そこで、これを明示的に行うためにこれら2つのメソッドが追加された。
Copyright© Digital Advantage Corp. All Rights Reserved.