【Python 雑談・雑学】 Common Mistake: mutable なオブジェクトを関数のデフォルト値に使っちゃダメでしょ、の話 投稿一覧へ戻る
Published 2020年5月27日13:15 by T.Tsuyoshi
悩んでいたので「どうしたの?」と声をかけたら ...
さて問題です。
print(a2) では何が出力されるでしょうか?
def enroll_class(name: str, attendee: str, attendees: list = []):
attendees.append(attendee)
return {
'name': name,
'attendee': attendee,
'attendees': attendees
}
a1 = enroll_class('math', 'Nana')
a2 = enroll_class('programming', 'Saki')
print(a2)
attendees.append(attendee)
return {
'name': name,
'attendee': attendee,
'attendees': attendees
}
a1 = enroll_class('math', 'Nana')
a2 = enroll_class('programming', 'Saki')
print(a2)
正解は
# {'name': 'programming', 'attendee': 'Saki', 'attendees': ['Nana', 'Saki']}
attendees リストの 'Nana' は何処から出てきたのでしょうか?
a2 = enroll_class('programming', 'Saki')
の何処にも 'Nana' は登場しないのに...
これは mutable なオブジェクトをデフォルト値として設定してしまったことから発生しています。
試しに、enroll_class() が呼び出されたときのそれぞれの attendees リストのアドレスを確認します。
def enroll_class(name: str, attendee: str, attendees: list = []):
print(id(attendees))
attendees.append(attendee)
return {
'name': name,
'attendee': attendee,
'attendees': attendees
}
a1 = enroll_class('math', 'Nana')
a2 = enroll_class('programming', 'Saki')
print(id(attendees))
attendees.append(attendee)
return {
'name': name,
'attendee': attendee,
'attendees': attendees
}
a1 = enroll_class('math', 'Nana')
a2 = enroll_class('programming', 'Saki')
# 106499592
# 106499592
a1、a2 の呼び出しでまったく同じリストを指しています。
これは関数に設定したデフォルト値が、呼び出されたときではなく定義されたときに評価されるからです。
しかも、List は mutable なオブジェクトですから、a2 の呼び出し時における attendees.append() では a1 の呼び出し時のリストに値が追加されて返された結果、だったんです。
この問題への対処方法は2つあります。
1つは mutable なオブジェクトをデフォルト値としてセットするのを止めて (というか、mutable なオブジェクトをデフォルト値としてセットしてはいけません!)、常に呼び出し元から引数として渡すことです。
def enroll_class(name: str, attendee: str, attendees: list):
attendees.append(attendee)
return {
'name': name,
'attendee': attendee,
'attendees': attendees
}
a1 = enroll_class('math', 'Nana', [])
a2 = enroll_class('programming', 'Saki', [])
print(a2) # {'name': 'programming', 'attendee': 'Saki', 'attendees': ['Saki']}
attendees.append(attendee)
return {
'name': name,
'attendee': attendee,
'attendees': attendees
}
a1 = enroll_class('math', 'Nana', [])
a2 = enroll_class('programming', 'Saki', [])
print(a2) # {'name': 'programming', 'attendee': 'Saki', 'attendees': ['Saki']}
もう1つは、デフォルト値としては None をセットし、関数内でチェックし対処する方法です。
def enroll_class(name: str, attendee: str, attendees: list = None):
if not attendees:
attendees = []
attendees.append(attendee)
return {
'name': name,
'attendee': attendee,
'attendees': attendees
}
a1 = enroll_class('math', 'Nana')
a2 = enroll_class('programming', 'Saki')
print(a2) # {'name': 'programming', 'attendee': 'Saki', 'attendees': ['Saki']}
if not attendees:
attendees = []
attendees.append(attendee)
return {
'name': name,
'attendee': attendee,
'attendees': attendees
}
a1 = enroll_class('math', 'Nana')
a2 = enroll_class('programming', 'Saki')
print(a2) # {'name': 'programming', 'attendee': 'Saki', 'attendees': ['Saki']}
自分が利用しているオブジェクトが immutable なのか mutable なのか、理解・把握しておくことはかなり重要です。
こちらの投稿にも興味があるかもしれません...
- 【Python 雑談・雑学 + coding challenge】Unicode の正規化処理 ( normalization ) を利用して、diacritical marks ( 発音区別符号 ) を取り除こう! テキスト解析の前処理としても重要です!
- 【Python 雑談・雑学 + coding challenge】文字列中の数字を抜き出して桁区切りをつけよう! 正規表現 (regular expression ) を使うと「えっ!?」っていうくらい簡単ですょ。lookahead と negative lookahead を使います。
- 【Python 雑談・雑学 + coding challenge】sorted 組み込み関数の key パラメータをうまく使って、カスタムオブジェクトを簡単にソートしよう! __getitem__、__len__ 特殊関数 ( special methods, dunder methods ) を実装すれば立派なシーケンス ( sequence ) です
- 【Python 雑談・雑学 + coding challenge】Python の pprint 機能を自分で実装してみよう! 自分なりの Pretty Print できちゃいます!!
0 comments
コメントはまだありません。
コメントを追加する(不適切と思われるコメントは削除する場合があります)