cover_effective_python

【 Effective Python, 2nd Edition 】assignment expression (walrus operator, :=) を利用して内包表記におけるサブ書式の重複を回避しよう! 投稿一覧へ戻る

Tags: Python , comprehension , Effective , assignment , walrus , expression

Published 2020年6月26日16:57 by T.Tsuyoshi

内包表記内の複数の箇所で同じ計算結果を参照したい、ということはよくあることです。


例えば、文房具を扱う会社の受注プログラムを作成するとします。
注文がきた時点で、発送可能な出荷単位以上の在庫があるかどうかを確認し、顧客に表示できるようにしましょう。


# 在庫
stock = {
'stapler': 99,
'notebook': 252,
'marker': 8,
'post-it': 43,
}


# 各商品の出荷単位
ship_unit = {
'stapler': 15,
'notebook': 20,
'marker': 8,
'post-it': 20,
}


# 注文きましたー!
order = ['marker', 'book-cover', 'post-it', 'notebook']


def get_units(name):
"""在庫商品の出荷可能単位数を返す"""
return stock.get(name, 0) // ship_unit.get(name, 1)


result = {}
for name in order:
units = get_units(name)
if units:
result[name] = units

print(result)

# {'marker': 1, 'post-it': 2, 'notebook': 12}




このロジックを辞書内包表記で記述します。


result = {name: get_units(name) for name in order if get_units(name)}

print(result)

# {'marker': 1, 'post-it': 2, 'notebook': 12}




この内包表記をみてバッと目に付くのは、get_units(name) が繰り返し記述されていることです。
これによってコードの簡潔さが失われちゃってますし、目にウルサいコードになってしまっています。
このように、内包表記内で同じ計算結果を参照したい、でも不要な繰り返しはしたくない、という場合の最も簡単な対処方法は、Python 3.8 で採用された walrus operator (:=) を利用することです。


result = {name: units for name in order if (units := get_units(name))}

print(result)

# {'marker': 1, 'post-it': 2, 'notebook': 12}




assignment expression を利用したおかげで、値の取得、変数へのセット、条件判定が1箇所で済んだばかりではなく、取得結果を使い回すことでコード全体がスッキリしました。
しかも、関数の呼び出し回数が減ったことによるパフォーマンスの向上も期待できます。


さて、assignment expression は内包表記内の値を設定するための書式中に記載することもできます。


result = {name: (tenth := count // 10) for name, count in stock.items() if tenth > 0}

# Traceback (most recent call last):
# NameError: name 'tenth' is not defined




しかし上の例では内包表記の評価順序を無視して変数を参照しようとしたためにエラーになっています。
つまり、for 文 -> if 文 -> 値の設定、という順序ですね。
if 文で tenth 変数を参照しようとしてもこの変数が定義されるのは最後の「値の設定」段階ですから、「まだ存在していませんよ」と怒られちゃうわけです。
このエラーを解決するには、assignment expression を if 文で記述し、そこで定義した変数を「値の設定」段階で参照するようにします。


result = {name: tenth for name, count in stock.items() if (tenth := count // 10) > 0}

print(result)

# {'stapler': 9, 'notebook': 25, 'post-it': 4}




assignment expression はもちろんジェネレータ書式内でも同様に利用できます。
辞書インスタンスではなく、商品名と在庫単位を返すイテレータを作成すると次のようになります。


found = ((name, units) for name in order if (units := get_units(name)))

print(found)

# <generator object <genexpr> at 0x000000000265D890>



print(type(found))

# <class 'generator'>



print(next(found))

# ('marker', 1)



print(next(found))

# ('post-it', 2)



print(next(found))

# ('notebook', 12)



print(next(found))

# Traceback (most recent call last):
# StopIteration

この投稿をメールでシェアする

0 comments

コメントはまだありません。

コメントを追加する(不適切と思われるコメントは削除する場合があります)