Python の代入式ってなに?


代入すると値が返されます。

>>> # 実行すると 0 が返されます。
>>> a := 0
0
>>> 
>>> # 値が返されるので代入できます。
>>> b = a := 1
>>> b
1
>>>


代入文と代入式を区別しましょう。

>>> # 代入文 ... 値が返されない。
>>> a = 1
>>> 
>>> # 代入式 ... 値が返される。
>>> a := 1
1
>>> 


PEP 572 で Python 3.8 から代入式が使えるようになりました。 PEP 572 に、なぜ代入式が必要か。代入式があれば綺麗になるコードの例が書かれています。
The importance of real code - PEP 572
Examples - PEP 572


ここでは代表例を3つ紹介しつつ、代入式がいかにコードが綺麗になるかと。 代入式を使わない場合の方法をそれぞれ示します。

1. 階段状にネストする if 文


copy モジュールのコードですね
https://docs.python.jp/3/library/copy.html

# 1. 問題のコード
reductor = dispatch_table.get(cls)
if reductor:
    rv = reductor(x)
else:
    reductor = getattr(x, "__reduce_ex__", None)
    if reductor:
        rv = reductor(4)
    else:
        reductor = getattr(x, "__reduce__", None)
        if reductor:
            rv = reductor()
        else:
            raise Error(
                "un(deep)copyable object of type %s" % cls)
# 2. 代入式による改善策
if reductor := dispatch_table.get(cls):
    rv = reductor(x)
elif reductor := getattr(x, "__reduce_ex__", None):
    rv = reductor(4)
elif reductor := getattr(x, "__reduce__", None):
    rv = reductor()
else:
    raise Error("un(deep)copyable object of type %s" % cls)
# 3. 代入文による代替策
#     switch-case 文みたいになった笑
for _ in range(1):
    reductor = dispatch_table.get(cls)
    if reductor:
        rv = reductor(x)
        break
    
    reductor = getattr(x, "__reduce_ex__", None)
    if reductor:
       rv = reductor(4)
       break
    
    reductor = getattr(x, "__reduce__", None)
    if reductor:
        rv = reductor()
        break

else:
    raise Error("un(deep)copyable object of type %s" % cls)

2. リスト内包表記の中で2度実行される関数


# 1. 問題のコード
new_lst = [f(x) for x in lst if f(x) > 0]
# 2. 代入式による改善策
new_lst = [y for x in lst if y:=f(x) > 0]
# 3. map による代替策
new_lst = [y for y in map(f, lst) if y > 0]

# 3. ジェネレータ式による代替策
new_lst = [y for y in (f(x) for x in lst) if y > 0]


map でいいんじゃないかなと思ったりもします。 Guido は map が嫌いですが、すでに定義された関数を使う場合は map は有効だと思います。

3. do-while 文が無いことによる煩雑さ


# 1. 問題のコード
while True:
    data = sock.recv(8192)
    print("Received data: ", data)
    if not data:
        break
# 2. 代入式による改善策
while data := sock.recv(8192):
    print("Received data:", data)
# 3. 代入文による代替策
data = sock.recv(8192)
while data:
    print("Received data:", data)
    data = sock.recv(8192)


問題のコードが、そんなに問題じゃないかなと思ったりします。

4. 代入式の何が問題か..

わからない。

代入式は賛否両論というか、一大論争を巻き起こしたらしく...

PEP 572 の議論は大炎上しました。 5月にLanguage Summit があってそこで議論のオーバーヒートをどう扱うかなどの話がされたのですが、 LWN.net がその まとめ を作ってくれたところから、 またML上での議論が再開し長大なスレッドになっています。。。


結果的に Guido は BDFL から引退しました。

いま PEP 572 の仕事は、終わった。私はこれ以上 PEP のために戦いたくないし、多くの人が私が決定したことを侮蔑するのを聞きたくない。
Now that PEP 572 is done, I don't ever want to have to fight so hard for a PEP and find that so many people despise my decisions.
和訳 Transfer of power - Guido van Rossum


個人的に辛い感じがする。Python は「あればいいものは、なくていい」みたいなスタイルだと思ってたから。 それに、もともと「ワンライナーは、嫌い」みたいな感じだったような気がする... どこかで議論されてるはずだから、探さないと...。

リスト内包表記で代入式を使うと if 文の中で代入されていて、ちょっと辛いと思いました。 y in lst と書かれていたら lst の要素が、そのまま y に代入されると思ってしまうからです。

今までは複雑な式を読み飛ばしても代入を見逃す危険は無かったのが、 PEP 572 が承認された場合は、自分が代入を見逃していないか疑いながらコードを読む必要が出てきます。 (引用したものの具体的に何をさしているか、理解できていません... orz)
最近のPython-dev(2018-04) - DSAS 開発者の部屋