Python で mutable と immutable の違い

簡単に言えば...

を変更できるオブジェクトのことを mutable と呼びます。

生成後に値を変更できないオブジェクトのことを immutable と呼びます。

3. Data model — Python 3.5.4 documentation

正確に言えば...


mutable 属性に直接代入されている
 オブジェクトを取り替えられる
immutable 属性に直接代入されている
 オブジェクトのを取り替えられない


 

しかしながら container (an immutable container object) は immutable であると判断されます、なぜなら container が所持しているオブジェクトの集合は変化していないからです。

however the container (an immutable container object) is still considered immutable, because the collection of objects it contains cannot be changed.

3. Data model — Python 3.5.4 documentation


いまいちピンと来ない、と言うかそもそも主語の an immutable container object って何だ?と思うので、順を追って見ていきたいと思います。

簡単な具体例

○ mutable なオブジェクト

例えば、list 型、 dict 型、ユーザが定義した型は mutable です。

>>> class Person():
...   def __init__(self, name):
...     self.name = name
... 
>>> 
>>> person = Person('yaruo')
>>> 
>>> person.name
'yaruo'
>>> 
>>> # value 値を変更できた -> mutable
>>> person.name = 'yarumi' 
>>> person.name
'yarumi'

○ immutable なオブジェクト

例えば、数値型、文字列型とタプル型のインスタンスは immutable です。

>>> a = 1
>>>
>>> # 1 の実部
>>> a.real
1
>>> 
>>> # 1 の虚部
>>> a.imag
0
>>> # 1 は immutable なので、値は変更できない。
>>> i.imag = 100
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'imag' of 'int' objects is not writable
>>> 

属性参照 (属性参照 を参照) や代入が失敗した場合に送出されます (オブジェクトが属性の参照や属性の代入をまったくサポートしていない場合には TypeError が送出されます)。

exception AttributeError

 

>>> t = ('yaruo', 'yaranaio')
>>> 
>>> t
('yaruo', 'yaranaio')
>>>
>>> t[0]
'yaruo'
>>> 
>>> # value 値を変更できない -> immutable
>>> t[0] = 'yarumi'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

組み込み演算または関数が適切でない型のオブジェクトに対して適用された際に送出されます。関連値は型の不整合に関して詳細を述べた文字列です。

exception TypeError

 

immutable なオブジェクトを自作するには一手間いるので、とりあえず数値型、文字列型とタプル型あたりは immutable だと抑えておけばいいのではないでしょうか。

immutable なオブジェクトをインスタンス化するクラスの一覧とかあったり、わかりやすいと思うんですけどね。

オブジェクトが mutable かどうかはその型によって決まります。例えば、数値型、文字列型とタプル型のインスタンスは immutable で、dict や list は mutable です。

An object’s mutability is determined by its type; for instance, numbers, strings and tuples are immutable, while dictionaries and lists are mutable.

3. Data model — Python 3.5.4 documentation

 

正確な具体例

tuple は immutable だけど、その中の値を取り替えられる。

>>> # tuple は immutable
>>> t = ([1,2],[3,4,5])
>>> t
([1,2],[3,4,5])
>>>
>>> # だけど代入ができる
>>> t[0][0]=100
>>> t
([100, 2], [3, 4, 5])

○ 詳しく見ていきましょう...

属性 attribute に代入される値 value を変更できないものを immutable って言う理解でいいのかな...?

さらにマニュアルの文言を見てみましょう。

従って immutable であること (immutability) は、厳密に言えば "値が変更できないこと" と同義ではなく、もう少し複雑です。

So, immutability is not strictly the same as having an unchangeable value, it is more subtle.

3. Data model — Python 3.5.4 documentation

 

え?違うの?immmutable なオブジェクトでも値を変更できるそうです。それはどのようなオブジェクトでしょうか?さらにさらにマニュアルの文言を見てみましょう。

mutable object への参照を持っている immutable container object の値は、参照している mutable object の値が変化させられた時に変化すると言えます。しかしながら container は immutable であると判断されます、

The value of an immutable container object that contains a reference to a mutable object can change when the latter’s value is changed; however the container is still considered immutable,


なぜなら container が所持しているオブジェクトの集合は変化していないからです。従って immutable であること (immutability) は、厳密に言えば "値が変更できないこと" と同義ではなく、もう少し複雑です。

because the collection of objects it contains cannot be changed. So, immutability is not strictly the same as having an unchangeable value, it is more subtle.

3. Data model — Python 3.5.4 documentation

 

immmutable なオブジェクトでも値を変更できる具体例として mutable object への参照を持っている immutable container object というのが出てきました。

○ "mutable object への参照を持っている immutable container object" とは何か?

これは一体何やねん。一つ一つ見ていきたいと思います。

Step1. object

「変数に代入できるもの」は、全てオブジェクトだと理解しています。

>>> a = 1
>>> b = 'Hello, world!'
>>> c = [1, 2, 3, 4]
>>> d = (1, 2, 3, 4)
>>> e = ['a':1, 'b':2, 'c':3]

Step2. immutable object

numbers, strings, tuples は immutable です。

オブジェクトが mutable かどうかはその型によって決まります。例えば、数値型、文字列型とタプル型のインスタンスは immutable で、dict や list は mutable です。

An object’s mutability is determined by its type; for instance, numbers, strings and tuples are immutable, while dictionaries and lists are mutable.

3. Data model — Python 3.5.4 documentation

Step3. container object

ほぼほぼ全てのオブジェクトが複数の属性を持っているので、ほぼほぼ全てのオブジェクトがcontainer オブジェクトだって認識でいいのではないでしょうか... int も複数の値を持ってますしね。

コンテナとはオブジェクトの集まりを表現するデータ構造、抽象データ型またはクラスの総称である。

コンテナ (データ型) - Wikipedia

Step4. immutable container object

Step2, 3 を踏まえると...
numbers, strings, tuples は immutable container object と言えそうですね。

Step5. mutable object への参照を持っている immutable container object

タプル t がそれに該当します。さっそく値を変更できる immutable なオブジェクトを見てみましょう。

>>> 
>>> #
>>> # a, b, c は mutable
>>> #
>>>
>>> class Obj():
...   def __init__(self, attr):
...     self.attr = attr
... 
>>> 
>>> a = Obj('nihao')
>>> b = Obj('hello')
>>> c = Obj('hola')
>>> 
>>>
>>> #
>>> # tuple t は immutable
>>> #
>>>
>>> #
>>> # mutable なオブジェクト a, b, c への参照を持つ
>>> # immutable なオブジェクト t
>>> # 
>>>
>>> t = (a, b, c)
>>>
>>>
>>> #
>>> # t はタプル immutable なので
>>> # 値を別のオブジェクトに変更できない。
>>> #
>>>
>>>
>>> t[2] = Obj('konnichiwa')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> 
>>>
>>>
>>> #
>>> # でも タプル t の要素 t[2], c は mutable なので
>>> # 値を別のオブジェクトに変更できる。
>>> #
>>>
>>> t[2].attr = "konnichiwa"
>>> 


mutable 属性に直接代入されている
 オブジェクトを取り替えられる
immutable 属性に直接代入されている
 オブジェクトのを取り替えられない

immutable なオブジェクトを生成するクラスを自作

こんなことできるんですね。

○ 1. デコレーター

引用先では value と言うデコレータを自作して簡単に immutable なオブジェクトを生成するクラスを自作できるようにしています。すごい...。
Python で immutable な値クラスを作るデコレータ
Python の デコレータ -

>>> # 属性はデコレーターに文字列で与える。
>>> @value('x', 'y')
... class Point():
...   def add(self):
...     return x + y 
... 
>>> 
>>> p = Point(1, 2)
>>>
>>> p.x = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

○ 2. namedtuple 関数

namedtuple 関数を用いて immutable なオブジェクトを生成するクラスを作ることができます。


インスタンスオブジェクトの動作確認

>>> import collections
>>>
>>> # immutable なクラスの定義
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>>
>>> p = Point(11, y=22)
>>> 
Point(x=11, y=22)
>>> 
>>> # 1) 属性の参照ができる
>>> p.x
11
>>> 
>>> # 2) 属性の変更はできない
>>> p.x = 33
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can t set attribute
>>>
>>>
>>> # 3) 属性の追加はできない
>>> p.z = 44
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'



クラスオブジェクトの動作確認

>>> import inspect 
>>> 
>>> # 4) Point はクラスオブジェクト
>>> inspect.isclass(Point)
True
>>>
>>> # 5) tuple を継承したクラスで実現されている 
>>> Point.__bases__[0]
<class 'tuple'>
>>>
>>> 
>>> # 6) クラスオブジェクトへの関数の追加ができる
>>> #    区別 クラスオブジェクト Point <-> インスタンスオブジェクト p
>>>
>>> def add(self):
...   return self.x + self.y
... 
>>>
>>> Point.add = add
>>>
>>> # 7) メソッドとして利用できる。
>>> p.add()
33

○ 3. Cpython 拡張

CPython なら Python そのものを拡張して immutable なオブジェクトのクラスを生成するクラスを作ることもできる様です。
How to make an immutable object in Python? - Stack Overflow
 


value の具体例

しれっとって言葉を使ってるけど、まず、そもそもってなんやねんって話になります。

答え: 属性 attribute に代入されているインスタンスオブジェクトのことを指していると "思われます" 。

この辺りの用語的な定義やら何やらを探してますが、まだそれと思しきものを見つけられないでいます...。

ただ、色々読んでると Python で言う値 value ってのは、このような理解に落ち着くのかなと思っています。

>>> a = 1
>>>
>>> # 1 という数字もインスタンスオブジェクト
>>> # なので属性を持つ
>>>
>>> # a の属性を表示
>>> for e in dir(a):e
... 
'__abs__'
... 略 ...
'__xor__'
'bit_length'
'conjugate'
'denominator'
'from_bytes'
'imag'
'numerator'
'real'
'to_bytes'
>>> # 1 オブジェクト
>>> a = 1
>>>
>>> # 1 オブジェクトの値 
>>> a.real
1
>>> a.imag
0
>>>
>>>
>>> # 1 + i オブジェクト(複素数)
>>> b = 1 + 1j
>>> 
>>> # 1 オブジェクトの値
>>> b.real
1.0
>>> b.imag
1.0

 

○ 属性と値の関係

でも、そう考えると、オブジェクトと値の関係も、再帰的に定義されてそうな気配がありますね。

object には複数の attribute がついていて
attribute には 1 つの value(object) が結びついてる的な...
object -*-> attribute -1-> value(object)

○ 実際のデータはどこにあるんだろう?

再帰的に定義されてるなら、 1 とか 'a' のような実際のデータ、リテラルは、どこに保存されてるのかな?と思ったのですが。

自己参照してるところが終点、実際にデータが保存されているところ、みたいな感じなのでしょうか...。

例えば int の 1 は自分自身 1 が属性に代入されています。

>>> a = 1
>>> 
>>> a.real
1
>>> a
1
>>>
>>> id(a.real)
4300950496
>>> id(a)
4300950496
>>> 

 

また str の 'W' には自分自身 'W' が 0 番目の要素に代入されています。

>>> s = 'Welcome to ようこそジャパリパーク! 今日もドッタンバッタン大騒ぎ うー!がぉー! 高らかに笑い笑えば フレンズ喧嘩して すっちゃかめっちゃかしても仲良し けものは居ても のけものは居ない本当の愛はここにあるほら 君も手をつないで大冒険 '
>>> 
>>> s[0]
'W'
>>> s[0][0]
'W'
>>> 
>>> id(s[0])
4473881520
>>> id(s[0][0])
4473881520

 

s[0]s の i 番目の要素です。s の属性とは違いそうですが、なんとなくこれが絡んでいそうな気がします。

s[i] s の 0 から数えて i 番目の要素
4.6.1. 共通のシーケンス演算

 

要素は属性とは違うやろ。って声が聞こえてきそうですが。なぜなら str には callable でない属性、メソッドでない属性が __doc__ しかないのです。だから、要素を属性的に考えてもいいのかな、と。

>>> for e in dir(s):
...   if not callable(getattr(s, e)):
...     e
... 
'__doc__'

 

Python の str class のデータ構造...

気になったので、簡単に調べて見ました。

Python のテキストデータは str オブジェクト、
すなわち 文字列 として扱われます。

文字列は Unicode コードポイント
イミュータブルな シーケンス です。

4.7. テキストシーケンス型 — str

 

1) コードポイントって何?(↓この記事は読むと鬼わかりやすい。)

文字集合では、個々の文字に対して、
文字集合内での符号位置が決められている。

これをコードポイントという。

文字コード考え方から理解するUnicodeとUTF-8の違い | ギークを目指して

 

2) シーケンスって何?
例えばリストのように lst[0] という記述で要素にアクセスできるオブジェクトを シーケンスと読んでいる "ようです"。