デフォルト値にミュータブルを設定するとキャッシュされる話
Pythonのドキュメンテーション "プログラミングFAQ" にある、 "なぜオブジェクト間でデフォルト値が共有されるのですか?" について、ちょっと読むだけだと分かりづらいところがあったので、挙動を確認してみました
ちなみにこの質問はPython2.7 / 3.4以降のドキュメンテーションでないと投稿されておらず、かつこの質問の原文へのリンクを踏むと何故か3.3の原文に飛ばされますので、URLの3.3を3とか3.4とかにすると原文読めます。
FAQの概要
このFAQの概要は、「デフォルト値にミュータブルなオブジェクトを設定するとキャッシュされるよ」ということです。ミュータブルなオブジェクトについては、こちら(3. データモデル - Python 3.4.2 ドキュメント)にあります。簡単に取り上げると、
…値を変更できるオブジェクトのことを mutable と呼びます。 生成後に値を変更できないオブジェクトのことを immutable と呼びます。…
とあります。具体的には、組み込まれているものだとlist
bytearray
set
dict
なんかがあります。
余談ですが、意味を重要視しているのか訳者の分担の都合なのか、原文で "mutable ~" となっている部分が和訳では「mutableな」「ミュータブルな」「変更可能な」と使い分けられていて、バーっと流し読みするとどこにミュータブルの事が書いてるのかよくわかりません。対義語のイミュータブルもそんな感じです。
挙動
>>> def foo(mutable=[]): ... print(mutable) ... mutable.append('bar') ... >>> foo() [] >>> foo() ['bar'] >>> foo() ['bar', 'bar'] >>> foo([]) [] >>> foo() ['bar', 'bar', 'bar']
ワオ。
どういうこったい
デフォルト値は、関数が定義されたときに一度だけ生成されるらしいです。Pythonが参照しまくってることと組み合わせれば、うっかり書いちゃいそうなこんなケースを作る事ができます。
>>> def foo(mutable=[]): ... mutable.append('bar') ... return mutable ... >>> bar = [] >>> baz = [] >>> bar.append(foo()) >>> baz.append(foo()) >>> baz.append(foo()) >>> bar [['bar', 'bar', 'bar']] >>> baz [['bar', 'bar', 'bar'], ['bar', 'bar', 'bar']]
ワオ。ワーオ。
ワオ。
うさげ
この仕様によってメモ化(FAQでは "memoizing" と紹介されていました)を実装し、キャッシュした値を返す事で関数を高速化する方法がFAQに紹介されています。是非一読を。また、キャッシュを回避する方法もFAQにあります。
いい仕様かどうかはここでは気にしないとして、私はこの仕様を気にした事は無いです。というか知りませんでした。とりあえず把握しておく系仕様って感じがします