こんな人にオススメ
python3.8から導入されたセイウチ演算子:=
(Walrus operator)ってどういう意味なの?
どんなときに便利になる?
ということで、今回はpython3.8以降で使うことができるセイウチ演算子:=
(Walrus operator)について解説する。その名の由来は演算子の:=が横向きのセイウチの顔に似ているから。可愛い。
セイウチ演算子はこれでしかできない処理というわけではなく、長くなるコードを短くすることができるワザのようなもの。
個人的には慣れていないということもあると思うけど、使い所が難しいと感じた。ただ、使いこなせるとかなりコードを短く書けると思う。
python環境は以下。
- Python 3.10.1
目次(クリック・タップでジャンプ)
作成したコード全文
下準備
import re
まずは下準備としてのimport
関連。今回は標準ライブラリであるre
だけ。re
についてはセイウチ演算子の説明上は必要ない。最後に応用的な感じで使うだけ。
セイウチ演算子は定義と代入と使用が同時に行える
# valが1かどうかの判定 val = 1 if val == 1: print('True!') print(val) # True! # 1
そもそもセイウチ演算子は変数の定義と値の代入と変数の使用を同時に行える文法のこと。例えば上の例だと変数val
を定義し、val
に1
を代入している。
次にif
でval
が1
か否かの判断をして、True
、すなわちval=1
ならprint
文で出力するというもの。この場合、定義と代入、値の使用という2STEP必要。
一方でセイウチ演算子を使用すると値の定義と代入と使用が1行で可能になる。val := 1
で定義と代入を、== 1
でif
の判定を行なっている。
# セイウチ演算子を使うと定義と判定を1行で行える if (val := 1) == 1: print('True!') print(val) # True! # 1
このようにセイウチ演算子は代入までというステップと、その変数の使用を一気に行うことができる演算子。
いちいち変数を定義しなくてもいいので、ちょっとスッキリ書ける。
セイウチ演算子を使うときはかっこに気をつける
# カッコをつけないと先に1 == 1の判定が行われて、その結果がvalに代入される if val := 1 == 1: print('True!') print(val) # True! # True
で、セイウチ演算子を使うときに気をつけないといけないことは、カッコを使わないと意味が変わるということ。
例えば上の例だとカッコを外して処理しているが、if
の中のprint(val)
の結果が1
ではなくTrue
になっている。
これは計算の優先順位が1 == 1→val := True
となっているから。val
に1
を代入してからif
の判定をしたいなら(val := 1)
とカッコを使う必要がある。
なお、この例は実質、以下と同じ意味になる。
# 意味合いとしては以下と同じ if val := (1 == 1): print('True!') print(val) # True! # True
通常の代入文としては使用できないこともない
val := 1 # val := 1 # ^^ # SyntaxError: invalid syntax
セイウチ演算子
は単独で使うことはできない。上のように単独使用すると文法エラーになる。
もちろんセイウチ演算子に必要な:
を消したら通常の代入になるから使用可能。
# 当たり前だが、:をなくせば代入できる val = 1 print(val) # 1
ただ、式全体をカッコでくくることで、通常の代入文のように使用することが可能。しかし、この書き方だと誤解を招きやすかったりシンプルに回りくどい書き方なのでおすすめしない。
また、PEP572でも以下のように非推奨として書かれている。
Unparenthesized assignment expressions are prohibited at the top level of an expression statement. Example:
y := f(x) # INVALID
(y := f(x)) # Valid, though not recommended
# カッコでくくると代入できるが、誤解されやすいかも (val := 1) print(val) # 1
tuple
で使用することも可能
# tupleでも代入できる (val := 1.1, val2 := 2.1) print(val, val2) # 1.1 2.1
セイウチ演算子はtuple
でくくることで並べて使用することも可能。
以下のように各セイウチ演算子をバラバラでカッコでくくっても代入することができる。もちろん、通常の代入文ではこのような代入の仕方はできない。
# それぞれのセイウチ演算子をカッコでくくっても可能 (val := 1.2), (val2 := 2.2) print(val, val2) # 1.2 2.2 # 通常の代入文だとこれはできない (val = 1.2), (val2 = 2.2) # (val = 1.2), (val2 = 2.2) # ^^^^^^^^^ # SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
ただ、わざわざセイウチ演算子を使わなくても通常の代入式で賄える。
# セイウチ演算子を使わなくても代入可能 val, val2 = 10, 20 print(val, val2) # 10 20 # 右辺がtupleでも可能 val, val2 = (100, 200) print(val, val2) # 100 200
複数変数への代入には注意が必要
val = 0 # valに10, val2に2が入ると期待 (val, val2 := 10, 2) # 実際にはval2 := 10が適用され、valはすでに定義されたval = 0が使われる print(val, val2) # 0 10
複数変数に一括で代入しようとするときは注意が必要。上のように書いたらval
には10
, val2
には2
が代入されそうだけど、実際にはval2
に10
が代入されてval
は変わらない。
なので新たに変数val3
を初めに定義しようとすると、そんな変数ないよってことでエラーとなる。
# val3を定義したいときに使うとエラーになる (val3, val2 := 10, 2) # (val3, val2 := 10, 2) # NameError: name 'val3' is not defined. Did you mean: 'val'?
while
文で使うときは注意
# 初期値0のaを1足しながら5未満まで出力 a = 0 while a < 5: print(a) a += 1 # 0 # 1 # 2 # 3 # 4
while
文でもセイウチ演算子は使える。上の例だと初期値0の変数a
を、a < 5
の条件下で1増やしながら値の出力を行なっている。
a
をループごとに増やしている部分をセイウチ演算子で置き換えると以下のようになる。一応、変数はb
に変更した。
注意点はwhile
での評価の時点でセイウチ演算子が適用されるので、初期値b=0
が適用されず、いきなりb += 1
が適用され、1
スタートとなる。
# 最初の時点で1足されるから、出力は1からになる b = 0 while (b := b + 1) < 5: print(b) # 1 # 2 # 3 # 4
format
やf-stringなど文字列で使う
# 通常の書き方 a = 10 b = 20 # print内の要素ごとにスペースが入るから「 , 」と[,」の間にもスペースが入る print('a =', a, ', b =', b, ', a + b =', a + b) # a = 10 , b = 20 , a + b = 30 # セイウチ演算子の書き方 print('a =', a := 10, ', b =', b := 20, ', a + b =', a + b) # a = 10 , b = 20 , a + b = 30
セイウチ演算子はprint
内でも使用可能。上の例だと予め定義しておいた変数をprint
で使っているが、セイウチ演算子を使うとprint
の中で定義しつつ出力することができる。
print
の中で,
を使って要素を使っているから、1行が長くなっているがこんな書き方もできる。
format
でも使用可能
# 通常の書き方 a = 10 b = 20 print("a = {a}, b = {b}, a + b = {c}".format(a=a, b=b, c=a + b)) # a = 10, b = 20, a + b = 30 # セイウチ演算子の書き方 print("a = {}, b = {}, a + b = {}".format(a := 10, b := 20, a + b)) # a = 10, b = 20, a + b = 30
いちいち,
で区切るのが面倒だし見通しが悪いというのなら、.format
を使用する手もある。この場合も同じように.format
のカッコ内でセイウチ演算子を使えばいい。
文字列部分と代入部分で分かれるから、多少は見通しが良くなるだろう。
f-stringで使用するときは注意
# 通常の書き方 a = 10 b = 20 print(f"a = {a}, b = {b}, a + b = {a + b}") # a = 10, b = 20, a + b = 30 # そのまま書くと書式設定の文字空けになってしまう # 5や15は何文字空けるかの指定になるので、実際の値はすでに定義した値が使われる print(f"a = {a := 5}, b = {b := 15}, a + b = {a + b}") # a = 10, b = 20, a + b = 30
と言っても.formatを
使うと、後ろに付け足す形になるから1行が長くなってしまう。ならf-string(f文字)を使えばいいんだけど、注意が必要。
f-stringの場合は文字列中の{}
に直接変数を入れるんだけど、セイウチ演算子の:を使うとフォーマットされてしまって、無駄に空白を入れることになる。
さらに、セイウチ演算子で代入できていないということは、新規の変数を定義すると、定義されていないということでエラーとなる。
# :=を使っても文字空けに使われるから、代入できていない print(f"c = {c := 5}, d = {d := 15}, c + d = {c + d}") # print(f"c = {c := 5}, b = {b := 15}, c + b = {c + b}") # NameError: name 'c' is not defined
ならどうするかというと、f-stringとセイウチ演算子を両立させたい場合は、セイウチ演算子の部分をカッコでくくればいい。
# セイウチ演算子が使いたかったらカッコを使う print(f"a = {(a := 30)}, b = {(b := 50)}, a + b = {a + b}") # a = 30, b = 50, a + b = 80
re
を使った応用例
# この文字列から数字だけを抽出したい data = '今日は10000歩、昨日は9000歩のウォーキング'
最後に標準ライブラリre
を使った応用例を紹介する。上の文字列から正規表現を使って数値部分だけを抽出する。
もし数値がなくて抽出するものがない、空白の状態なら何も出力せず、数値があるなら出力するというコードを書くと例えば以下のようになる。
# 通常の書き方 # re.findallで数値部分だけを抽出 result = re.findall(r'\\d+', data) if result: # dataに数値が入っていればTrue print(result) # ['10000', '9000']
この場合は一旦result
という変数で数値部分を取り出し、それをif
で要素数0の空のlist
ではないことを判定してから出力している。
一旦result
という変数を使うのをやめる場合、以下のように書くことができるが、この場合はprint
の際にもre.findall
で数値部分の抽出を行う必要があり、かなり冗長。
# ifに直接代入するならdainyuusurunara2回書かないといけない if re.findall(r'\\d+', data): print(re.findall(r'\\d+', data)) # ['10000', '9000']
一旦、変数に置きたくもないしprint
するときにもスッキリ書きたいならセイウチ演算子を使うとどっちの願いも叶う。
以下のようにif
の判定時にセイウチ演算子を使用してあげると、result
を定義して代入しつつif
判定可能。print
の際には代入したresult
を使用することができる。
# セイウチ演算子の書き方 # スッキリする if (result := re.findall(r'\\d+', data)): print(result) # ['10000', '9000']
ただ、セイウチ演算子を使うとif
の判定がごちゃつくのがネック。どれも一長一短。
使い所が難しい
今回はpython3.8以降で実装されたセイウチ演算子について解説した。確かに短く書ける部分あるが、短くする代わりに1行が長くなる。
個人的には1行が長くなると処理がややこしくなって理解しにくいような気がするので、使い勝手が悪いようにも感じる。
ただ、if
で簡単な判定をしつつ、代入もしたいときには使えるかもしれない。難しい。
関連記事
-
【python&初級】のlistとかforとかifとかまとめ
続きを見る
-
【python&csv読み込み】pythonを使ってcsvを読み込み
続きを見る
-
【python3&zip関数】python3系にてzipは一回使ったら消える
続きを見る
-
【python&フィッティング】polyfitとcurve_fitでfitting
続きを見る