【Python 雑談・雑学】 Common Mistake: mutable なオブジェクトを関数のデフォルト値に使っちゃダメでしょ、の話 投稿一覧へ戻る
Published 2020年5月27日13:15 by mootaro23
SUPPORT UKRAINE
- Your indifference to the act of cruelty can thrive rogue nations like Russia -
悩んでいたので「どうしたの?」と声をかけたら ...
さて問題です。
print(a2) では何が出力されるでしょうか?
正解は
attendees リストの 'Nana' は何処から出てきたのでしょうか?
a2 = enroll_class('programming', 'Saki')
の何処にも 'Nana' は登場しないのに...
これは mutable なオブジェクトをデフォルト値として設定してしまったことから発生しています。
試しに、enroll_class() が呼び出されたときのそれぞれの attendees リストのアドレスを確認します。
a1、a2 の呼び出しでまったく同じリストを指しています。
これは関数に設定したデフォルト値が、呼び出されたときではなく定義されたときに評価されるからです。
しかも、List は mutable なオブジェクトですから、a2 の呼び出し時における attendees.append() では a1 の呼び出し時のリストに値が追加されて返された結果、だったんです。
この問題への対処方法は2つあります。
1つは mutable なオブジェクトをデフォルト値としてセットするのを止めて (というか、mutable なオブジェクトをデフォルト値としてセットしてはいけません!)、常に呼び出し元から引数として渡すことです。
もう1つは、デフォルト値としては None をセットし、関数内でチェックし対処する方法です。
自分が利用しているオブジェクトが immutable なのか mutable なのか、理解・把握しておくことはかなり重要です。
さて問題です。
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 なのか、理解・把握しておくことはかなり重要です。
この記事に興味のある方は次の記事にも関心を持っているようです...
- People who read this article may also be interested in following articles ... -