Python学習チャンネル by PyQ

Pythonのオンライン学習プラットフォームPyQのオフィシャルブログです

関数のデフォルト引数の値が、変わるのはなぜ?

f:id:kenken0326:20210406160039p:plain

PyQチームのtsutomuです。
今日は、仮引数のデフォルト値を指定する際の注意点について紹介します。

Pythonでは、仮引数のデフォルト値にリストや辞書などのミュータブルなオブジェクトを指定すると、デフォルト値が意図しない値に変わることがあります。
その理由と、解決方法を紹介します。

質問

下記のコードを書いたら、3番目の呼び出しで意図しない結果(['first', 'third'])が表示されました。
デフォルト引数に設定した値は空のリスト( [])なので、 'third' だけ表示されるのが正しいと思うのですが、なぜ'first'も表示されるのでしょうか?

書いたコード

def append_and_print_list(msg, args=[]):
    args.append(msg)
    print(args)
append_and_print_list('first')  # 1番目
append_and_print_list('second', [])  # 2番目
append_and_print_list('third')  # 3番目

出力結果

['first']
['second']
['first', 'third']

期待する結果

['first']
['second']
['third']

回答

仮引数のデフォルト値の代入は、定義時に一度だけ実行されます

そのため、リストや辞書などのミュータブルな値を仮引数のデフォルトに指定すると意図しない処理になることがあります。

リストや辞書などを仮引数のデフォルトに指定するのは、避けた方がよいでしょう。

仮引数のデフォルトにリストや辞書を避けた方がいい理由

質問のコードを実行すると以下のような処理が行われます。

f:id:kenken0326:20210406155213j:plain

引数を省略して関数を呼び出すと、デフォルト値には定義時のオブジェクトが使われます

そのため、定義時と1番目・3番目の実行時のargsは、同じリストが使いまわされます。

つまり、図の右上のように、1番目と3番目のargsは同じ[]を指します。
3番目の実行では、1番目で追加された'first'が存在しているので、['first', 'third']と出力されます。
2番目の実行時では、実引数で明示的に別の空のリスト[]を指定しているため、定義時のオブジェクトとは違うリストになり、['second']だけが出力されます。

仮引数のデフォルトにリストや辞書を使いたい場合

どうしても仮引数のデフォルトにリストや辞書を使いたい場合は、 Noneなどのイミュータブルな値をデフォルト値とし、デフォルト値の場合に別途初期化するといいでしょう。

def append_and_print_list(msg, args=None):
    if args is None:
        args = []
    args.append(msg)
    print(args)

例えば、今回の質問内容のコードの場合、 argsのデフォルト値をNoneにし、関数内の処理でNoneかどうかを毎回確認することで、[]で初期化できるようになります。

関連するクエスト

このお悩み解決はPyQのクエスト「仮引数の使い方を学ぼう」の6問目の補足情報として作成しています。

Copyright ©2017- BeProud Inc. All rights reserved.