セイウチ演算子

このページでは、Python3.8から登場したセイウチ演算子(代入式)について解説します。

代入における文と式

普段あまり意識をすることがありませんが、プログラミングコードを構成するものに文と式があります。かなり雑な説明ですが、それぞれ以下のような特徴があります。

  • 式・・・何かしらの評価値を持ち、それ返す
  • 文・・・手続きを表す(値を持たない、返さない)

例えば、Pythonのif文自体は値を返さないのでその名の通り文となります。一方、演算はその結果を取り出すことができるので式となります。正確な説明はWikipedia等を参照してください。

式 (プログラミング) - Wikipedia
文 (プログラミング) - Wikipedia

さて、Pythonにおいて代入は代入文とも呼ばれ値を返しません。このため、変数になにか値を代入し、それをさらに計算や評価につかう、という処理を1行で記述することはできません。例えば変数bに2を代入し、bと3の和をaに代入しようとしても以下のようなコードはエラーとなります。

a = (b = 2) + 3
# SyntaxError: invalid syntax

この一方、C言語をはじめ一部のプログラミング言語では代入は代入式と呼ばれ、代入の記述からさらに値を取り出すことが可能です。以下のコードはC言語のコードですが、変数bに2を代入し、bと3の和をaに代入しています。代入が式として振る舞っていることがわかります。

#include <stdio.h>

int main(void)
{
    int a;
    int b;
    a = (b=2) + 3;
    printf("%d %d\n", a, b);
    return 0;
}

セイウチ演算子による代入式

代入における文と式について理解したところでいよいよセイウチ演算子について解説します。Python3.8以降、セイウチ演算子と呼ばれる演算子「:=」を使用することでPythonでも代入式が使えるようになりました。特徴的な名称ですが、演算子:=がセイウチの目と牙に似ているためこのような愛称がつけられました。冒頭の先程のコードがエラーだったのはbへの代入が文であったためです。これをセイウチ演算子に変えると代入の記述が式となるため動作するようになります。

a = (b:=2) + 3
print(a, b) # 5, 2

条件式での活用

よく挙げられる使い所としてif文の条件式において代入と評価を同時に行う方法があります。例えば、以下のコードでは1行目で変数bに5を代入し、それが3より小さいかどうかを評価しています。

if (b := 5) > 3:
    print(str(b) + " > 3")

これだけだとメリットが分かりづらいためもう1つ例です。例えば、リストの長さが3を超える場合にその長さをprint出力する場合について考えてみましょう。以下のようなコードが考えられますが、len関数が2回呼び出されているのがイマイチですね。

my_list = [1, 3, 5, 5, 8]
if len(my_list) > 3:
    print("リストの長さ:" + str(len(my_list)))

代入式を使用すると以下のように記述することができます。

my_list = [1, 3, 5, 5, 8]
if (n := len(my_list)) > 3:
    print("リストの長さ:" + str(n))

あくまでも個人的な考えなのですが、制御ブロックの外側でも変数を使用する場合は条件式で代入式を使用するのは避けたほうが良いかもしれません。例えば以下のようなコードの場合、読み手は変数nはif文にまつわるものかと勘違いしてしまう可能性がありますが、実際はコード後方でも使用しているため混乱を招きます。

my_list = [1, 3, 5, 5, 8]
if (n := len(my_list)) > 3:
    print("リストの長さ:" + str(len(my_list)))

:
:

# if文の外側でもnを使う
m = n + 1     # このnは一体どこから出てきた??

以下のようにスコープ外で定義したほうが読み手に優しいコードと言えそうです。Pythonは他の言語と比較して変数のスコープが緩いためこういった配慮が必要です。

my_list = [1, 3, 5, 5, 8]
n = len(my_list)
if n > 3:
    print("リストの長さ:" + str(len(my_list)))

:
:

# if文の外側でもnを使う
m = n + 1

内包表記での活用

もう1つ、セイウチ演算子が真価を発揮するのが内包表記です。以下のような内包表記内部のif文での変数を使用したい場合に活躍します。以下のコードでは、0~9の整数xについて、関数y = x^2 - 1の値yが1を超えるもののみのリストを構築しています。

data = range(10)

def f(x):
    return x**2 - 1

new_list = [y for x in data if (y := f(x)) > 1]
# [3, 8, 15, 24, 35, 48, 63, 80]

セイウチ演算子を使わない場合、それなりに面倒なコードになりそうです。

色々使い所の多そうなセイウチ演算子ですが、その他の例や実装の経緯がPEP572にまとまっているため、合わせて参照することをおすすめします。
PEP 572 -- Assignment Expressions