Python

【Python】ミュータブルとイミュータブル

この記事では、Pythonのミュータブル(mutable)とイミュータブル(immutable)について解説します。

ミュータブルとイミュータブルとは

値を変更できるオブジェクトのことを「ミュータブル」と呼び、値が変更できないオブジェクトのことを「イミュータブル」と呼びます。

値が変更できるかどうかと言いましたが、ここで重要なのは「同じオブジェクトのまま、値が変更できるかどうか」ということです。

例えば、int型はイミュータブルですが、以下のコードを見てください。

num = 1

num = 2

これは、値を変更しているかに見えますが、単に異なるオブジェクトを代入しているに過ぎません。

同じオブジェクトのまま値を変更するには、関数やプロパティを使えばいいですが、int型はイミュータブルなので、そんな関数は実装されていません。

なので、プロパティから直接書き換えてみましょう!

num = 1

# すべてAttributeError
num.real = 2
num.imag = 2
num.numerator = 2
num.denominator = 2

すべてAttributeErrorが発生しました❗️

このように、同じオブジェクトのまま値が変更できないオブジェクトをイミュータブルと呼びます。反対にミュータブルは、同じオブジェクトのまま値を変更することができます。

l = [1, 2]
l.append(3)

print(l)
>>  [1, 2, 3]

ミュータブルとイミュータブル一覧

ミュータブルな型

ミュータブルの型には、以下のようなものがあります。

  • list
  • dict
  • set
  • bytearray
  • ユーザー定義クラス

イミュータブルな型

イミュータブルの型には、以下のようなものがあります。

  • bool
  • int
  • flaot
  • complex
  • str
  • tuple
  • range
  • bytes
  • file object

id()関数

先ほどまでは、なんとなく雰囲気で同じオブジェクトとして認識していましたが、id()関数を使うことで確認することができます。

idとは、オブジェクトが生成された時に付けられる番号で、オブジェクトごとに異なるidを持っています。

num = 1
print(id(num))
>> 140664210856240

num = 2
print(id(num))
>> 140664210856272


vals = []
print(id(vals))
>> 140664213163968

vals.append(1)
print(id(vals))
>> 140664213163968

同じIDを参照するイミュータブル

少し補足的なことですが、イミュータブルな型の同じ値のインスタンスが複数ある場合、同じIDを参照することがあります。

例えば、int型で同じ値のインスタンスを生成し、IDを比較してみます。

num1 = 10
num2 = 10

# isでIDが同じかどうか調べる
print(num1 is num2)
>> True

同じIDを参照していました。

int型は、メモリ内に -5~256 までの整数がいつでも使えるようにあらかじめ用意されています。なので、新しいオブジェクトを生成しておらず、すでに用意されているオブジェクトを参照するのでIDが同じなります。

しかし、他のイミュータブルな型や範囲外(-5~256以外)のint型の値でも同じIDを参照します。

i1 = 257
i2 = 257
print(i1 is i2)
>> True

f1 = 1.5
f2 = 1.5
print(f1 is f2)
>> True

t1 = (1, 2, 3)
t2 = (1, 2, 3)
print(t1 is t2)
>> True

これは、インタープリタによって同じ値のオブジェクトは同じオブジェクトを使うように最適化されているからです。

ただし、例外としてミュータブルな要素が格納されたタプルは、異なるIDを参照します。

t1 = (1, 2, 3, [])
t2 = (1, 2, 3, [])
print(t1 is t2)
>> False

例外的なイミュータブル

イミュータブルな型は、同じオブジェクトのまま値を変更できないと言ってきましたが、例外的なものがあります。

それは、「ミュータブルな要素を持つイミュータブル」です。

例えば、タプルの中にリストが格納されている場合などです。

t = (1, 2, [])
identity = id(t)  # 変更前のID

t[2].append(99)
print(t)
>> (1, 2, [99])

# 変更後のIDと比較
print(identity == id(t))
>> True

このように、ミュータブルな要素を持つイミュータブルは、IDを変更せずとも値を書き換えることができます。

最短3か月でエンジニア転職『DMM WEBCAMP COMMIT』