:= (ウォルラス演算子)の使い方

walrus 908609 1280

skeeze による Pixabay からの画像

Python の := 演算子について説明します。

:= 演算子は Python のバージョン 3.8 ( 2019 年 10 月リリース)で導入されました。該当する PEP は PEP 572 です。

呼び方

呼び方は、 := 演算子のことは walrus operator:= を含む式全体のことは assignment expressions と呼ぶのが主流のようです。

日本語では、前者はカタカナで ウォルラス演算子 、あるいは訳して セイウチ演算子 ( walrus = セイウチ )でしょうか。 assignment expression は別の言語で一般的な 代入式 がよいですかね。

PEP 572 には named expressions と呼ばれることもあると書かれています。

ウォルラス演算子という呼び名は日本ではあまり流行らなさそうな感じもしますが、本記事内ではウォルラス演算子と代入式という表現で行きます。

ウォルラス演算子とは

Python のウォルラス演算子は既存の = 演算子で作れる代入文( assignment statements )と同じく、名前(≒変数)に値をひもづけるための演算子です。

既存の = ・代入文は、別の式の中に組み込んで使うことができませんでした。ウォルラス演算子を使うことで、式の途中結果を名前にひもづける(≒代入する)ことができます。

JavaScript ・ Ruby ・ PHP 等 Python に似たスクリプト言語には以前から代入式が存在していたため、それらの言語をメインで使い Python をサブで使うような方には Python の既存の代入文( = )は融通が利かない・非直感的な感じに感じられたことと思います。

使い方

ウォルラス演算子・代入式は次のように使うことができます。

import random

def getvalue():
    if random.random() > 0.2:
        return 'OK'

    return None

if value := getvalue():
    print(value)

while value := getvalue():
    print(value)

このプログラムを実行すると OK という文字がランダムに 2 〜 7 行ほど表示されて終了します。

ウォルラス演算子が登場するまでは、この if 文と while 文のところはそれぞれ次のように書く必要がありました。

value = getvalue()
if value:
    print(value)
while True:
    value = getvalue()
    if value:
        print(value)
        continue
    break

ウォルラス演算子をうまく使うことでコードが短くわかりやすくなります。

メリット・使いどころ

ウォルラス演算子・代入式のメリットは、既存の代入文( = )では短く書けなかったコードが短く書けてコードがコンパクトになることです。

ただし、他の言語における代入式と同様に、濫用するとかえってわかりづらくなったりバグの温床になったりするので、むやみやたらと使うのではなく、使えばコードがよくなるところでだけ使うのがよいと思います。

使いどころのひとつは、 callable (関数やメソッド等)が必ず truthy な値か falsy な値を返し、 truthy な値を返す場合にだけ特定の処理を行いたいような場合 です。

while stream := getstream():
    process(stream)
if match := re.match(pattern, text):
    print(match)

PEP 572 には、標準ライブラリの rere.match()re.search() の例が紹介されています:

if match := re.search(pat, text):
    print("Found:", match.group(0))
elif match := re.search(otherpat, text):
    print("Alternate found:", match.group(0))
elif match := re.search(third, text):
    print("Fallback found:", match.group(0))

ウォルラス演算子による代入式は別の式の途中に差し込めるので、 デバッグのためにメソッドチェーンの途中の値を確認したいとき なんかにも便利かもしれません。例えば次のようなクラスがあるとします。

class A:
    def f1(self):
        ...
        return self

    def f2(self):
        ...
        return self

    def f3(self):
        ...
        return self

これを次のように利用している場合を考えます。

A().f1().f2().f3()

この途中にウォルラス演算子を差し込むことで値を取り出せます。

(f2_result := A().f1().f2()).f3()
logger.debug(f2_result)

その他、 lambda 式や三項演算(インラインの ifelse )との組み合わせでも価値を発揮してくれそうです。

留意点

ウォルラス演算子・代入式にはいくつか注意点があります。

スコープを作らない

ウォルラス演算子は新たなスコープを作るわけではありません。ウォルラス演算子で値にひもづけられた名前は、(削除しないかぎり)既存のスコープの末尾まで存在し続けます。 JavaScript の let のような挙動を期待して利用すると思わぬバグを生む可能性があります。

単独で文にすることはできない

ウォルラス演算子・代入式を既存の = ・代入文の代わりに使うことはできません。

次のコードを実行すると SyntaxError があがります。

val := 5
# => SyntaxError: invalid syntax

しかし、次のコードは問題なく通ります。

(val := 5)

次のコードも大丈夫です。

val2 = (val1 := 5)

ただし、このような使い方(=本来通常の = を使うべきところで := を使うこと)は紛らわしいので原則使わない方がよいと思います。 PEP でも非推奨と書かれています。

= と同じ挙動ではない

ウォルラス演算子・代入式の挙動は既存の = ・代入文と必ずしも同じではありません。

例えば、 = の代入では右辺が tuple の場合にそのかっこを省略することができますが、ウォルラス演算子の場合は右辺の tuple のかっこを省略することができません。かっこ無しで , 区切りで複数の値を右辺に書くと思いも寄らない挙動になります。

val1 = 3, 4
print(val1)
# => (3, 4)

(val2 := 3, 4)
print(val2)
# => 3

(val3, val4 := 3, 4)
NameError: name 'val3' is not defined

所感

いち Python ユーザーとしての個人的な感想です。

:= 演算子が Python に導入されると初めて聞いたときは「えー」と思いましたが、時間が経つうちに慣れてきて、いまはそれほど違和感を感じません。

ただし、従来の代入文と新しい代入式とで書き方のパターンが増えてしまうので、 Python らしいかというとあまり Python らしくはないと思います。しかし、代入式を使うときれいに書けそうなパターンがいくつか思いつくので、便利かそうでないかというと便利だと思います。

もう少し引いた視点から見ると、 Python はいま「最も使われているプログラミング言語のひとつ」でかつ「最もシェアが伸びているスクリプト言語」です。そのあたりの状況を自覚して(?)このあたりの仕様をちょっと緩くするという判断はとてもよい気がします。

以上です。というわけで、 Python の := 演算子についてでした。

参考