3秒でわかる
生成したあとで中身を書き換えられる性質のこと。Python のリストや辞書がこれにあたり、共有されると意図しない変更が伝わります。
もう少し詳しく
どういうものか
ミュータブルは、オブジェクトを作ったあとに、そのオブジェクト自身を書き換えられる性質です。Python ではリスト、辞書、集合が該当します。反対にイミュータブルなのは、数値、文字列、タプルです。
見分ける手がかりは id です。書き換えても同じ id のままなら、そのオブジェクト自体が変わっています。
nums = [1, 2]
print(id(nums))
nums.append(3)
print(id(nums)) # 同じ id のまま中身だけ増える
s = "ab"
print(id(s))
s += "c"
print(id(s)) # 別の id。新しい文字列が作られているなぜ必要か
この区別を知らないと説明できないバグがあります。代表的なのは、リストを別の名前に代入したつもりで、実は同じ実体を指しているケースです。
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] a も増えている
c = a.copy() # 別の実体を作る
c.append(5)
print(a) # [1, 2, 3, 4] こちらは影響なし関数に渡したときも同じで、リストを受け取った関数が append すると、呼び出し元の変数が変わります。値を渡したのか実体を渡したのかを意識する必要があります。
具体例
もっとも有名な落とし穴が、デフォルト引数にリストを置く書き方です。
def add_item(item, cart=[]): # 危険
cart.append(item)
return cart
print(add_item("りんご")) # ['りんご']
print(add_item("みかん")) # ['りんご', 'みかん'] 前回の中身が残る
def add_item_safe(item, cart=<a href="/glossary/none" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">None</a>): # 正しい書き方
if cart is None:
cart = []
cart.append(item)
return cartデフォルト値は関数を定義した瞬間に1つだけ作られ、呼び出しごとには作り直されません。ミュータブルなものを置くと、その1つを全呼び出しで共有します。
つまずきやすいところ
タプルはイミュータブルですが、中にリストを入れると、そのリストは書き換えられます。タプル自体の要素の並びは固定でも、要素として指している先まで凍るわけではありません。
辞書のキーにリストを使えないのも同じ理由です。中身が変わるとハッシュ値が変わり、置いた場所を見失うため、キーにはイミュータブルなものしか許されません。
似た用語との違い
| 分類 | Python の例 | 書き換え |
|---|---|---|
| ミュータブル | list, dict, set | 同じ実体のまま変わる |
| イミュータブル | int, str, tuple | 変えると別の実体になる |