# イミュータブルってなに?

# 1. 簡単に言えば...


変更できないオブジェクトのことをイミュータブルと言います。 反対に変更できるオブジェクトのことをミュータブルと言います。

値を変更できるオブジェクトのことを mutable と呼びます。
Objects whose value can change are said to be mutable;

値を変更できないオブジェクトのことを immutable と呼びます。
objects whose value is unchangeable ... are called immutable.

3.1. オブジェクト, 値 そして 型 - Python 言語リファレンス
3.1. Objects, values and types - The Python Language Reference

immutable - 用語集
(イミュータブル) 固定の値を持ったオブジェクトです。イミュータブルなオブジェクトには、 数値、文字列、およびタプルなどがあります。これらのオブジェクトは値を変えられません。 別の値を記憶させる際には、新たなオブジェクトを作成しなければなりません。 イミュータブルなオブジェクトは、固定のハッシュ値が必要となる状況で重要な役割を果たします。辞書のキーがその例です。

値という文字を薄くしておきました。 とりあえずいまは、変更できるオブジェクトは mutable, 変更できないオブジェクトは immutable と考えていただいて差し支えありません。

# ◯ mutable なオブジェクト

例えば、list 型、 dict 型、class 定義文で定義したクラスは mutable です。

# class 定義文で定義したクラス

class Person:
    def __init__(self, name):
        self.name = name

person = Person('yaruo')
person.name

# 変更できた -> mutable
person.name = 'yarumi' 
person.name  # 'yarumi'
>>> # 変更できた -> mutable
... person.name = 'yarumi' 
>>> person.name  # 'yarumi'
'yarumi'
>>> 

# list 型

lst = [1, 2, 3]

# 変更できた -> mutable
lst[2] = 4
lst  # [1, 2, 4]
>>> # 変更できた -> mutable
... lst[2] = 4
>>> lst  # [1, 2, 4]
[1, 2, 4]
>>> 

# ◯ immutable なオブジェクト

例えば、int, str, bool と tuple のインスタンスは immutable です。

# int 型

a = 1

# 1 の実部
a.real  # 1

# 1 の虚部
a.imag  # 0

# 変更できない -> immutable
i.imag = 100  # AttributeError
>>> # 変更できない -> immutable
... i.imag = 100  # AttributeError
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: name 'i' is not defined
>>> 

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

# str 型

s = 'ランボー/怒りの脱出'

s[0]  # 'ラ'

# 変更できない -> immutable
s[0] = 'チ'  # TypeError
>>> # 変更できない -> immutable
... s[0] = 'チ'
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: 'str' object does not support item assignment
>>> 

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

こう言うコード見せられると「やめろっ!!」って思いますよね。 まっ、エラーになるんですけど笑 あと正直、この時の AttributeErrot と TypeError の違いが、あまりよくわかっていません。

# ◯ immutable な型の一覧

以下に immutable な型を列挙します。

  • int
  • float
  • str
  • tuple
  • bool
  • range
  • type(None)

# ◯ よくある誤解

変数に代入できるから mutable だというのは誤りです。

a = 1
a = 2  # <- 変数 a に代入できたから int 型は mutable だよね!?

何故なら、変数に代入してもオブジェクトは変化しないからです。 反対に属性に代入できた場合は、オブジェクトが変化します。 変数への代入と属性への代入の違いについては、前回の記事で見てきました。


# 2. 正確に言えば...

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


例えば tuple はオブジェクトを変更することができます。 ですが immutable に分類されます。

# tuple は immutable だけど...
person = ('サーバル', 17, ['かばん', 'ラッキービースト'])

# オブジェクトを変更できる -> だけど immutable
person[2].append('アライグマ')

person

>>> person
('サーバル', 17, ['かばん', 'ラッキービースト', 'アライグマ'])
>>> 

# ◯ 変更できる immutable なオブジェクト

mutable object への参照を持っている immutable container object は、値が変更できますが immutable です。

mutable object への参照を持っている immutable container object の値は、 参照している mutable object の値が変化させられた時に変化すると言えます。 しかしながら container (an immutable container object) は 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 (an immutable container object) is still considered immutable,
3.1. Objects, values and types

なんで?どうして?

なぜなら 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.1. Objects, values and types

# ◯ mutable object への参照を持っている
immutable container object ってなに?

答え: mutable なオブジェクトが属性に代入された immutable なオブジェクト

例えば、以下の変数person に代入されたオブジェクトがそうです。 一つ一つ見ていきたいと思います。

# tuple は immutable だけど...
person = ('サーバル', 17, ['かばん', 'ラッキービースト'])

# オブジェクトを変更できる -> だけど immutable
person[2].append('アライグマ')

person

# 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

int, strings, tuples は immutable です。

オブジェクトが mutable かどうかはその型によって決まります。 例えば、数値型(int, float などの総称か)、 文字列型とタプル型のインスタンスは 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.1. Objects, values and types - The Python Language Reference

# Step3. container object

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

container - Python 言語リファレンス 他のオブジェクトに対する参照をもつオブジェクトもあります; これらは コンテナ (container) と呼ばれます。 コンテナオブジェクトの例として、タプル、リスト、および辞書が挙げられます。オブジェクトへの参照自体がコンテナの値の一部です。 — ワイの注記 container について記述されている箇所の抜粋しました。 タプル、リスト、および辞書など集合を表現するオブジェクトを container だと言いたい様子。 ただ、この定義だと全てのオブジェクトが container に該当してしまうんじゃまいか..

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

# Step4. immutable container object

Step2, 3 を踏まえると... int, str, tuple は immutable container object と言えそうですね。

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

答え: mutable なオブジェクトが属性に代入された immutable なオブジェクト

タプルがそれに該当します。 さっそく変更できる immutable なオブジェクトを見ていきたいと思います。

# list である friends_list は mutable です。
friends_list = ['かばん', 'ラッキービースト']


# tuple である person は immutable です。
person = ('サーバル', 17, friends_list)

# そのため直下のオブジェクトは変更できません。
person[0] = ['ワイ']
>>> person[0] = ['ワイ']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> 

しかし タプルの要素 person[2] に代入されたリストは mutable なので 値を別のオブジェクトに変更できます。

# list である friends_list は mutable です。
friends_list = ['かばん', 'ラッキービースト']

# tuple である person は immutable です。
person = ('サーバル', 17, friends_list)

# オブジェクトを変更できる -> だけど immutable
person[2].append('アライグマ')

person
>>> person
('サーバル', 17, ['かばん', 'ラッキービースト', 'アライグマ'])
>>> 

# ◯ まとめ

変更できないオブジェクトをイミュータブルと呼びます。 ただし、属性の属性、あるいは要素の要素は変更できても、イミュータブルと呼んで良いです。

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

# 3. イミュータブルの重要性

なんでイミュータブルは重要なのでしょうか? たかだか変更できないというだけのことに、 イミュータブルという名前までつけて有難がっているのでしょうか? 「副作用」という言葉を軸にして、以下の記事で考えてきました。

# 4. イミュータブルの一覧

WARNING

ここから下は個人的な興味で、何がイミュータブルが調べようとしました。 読まなくても一切問題ありません。

公式ドキュメントで immutable なクラスの一覧というのを探し回ったのですが、見当たりませんでした。

代わりにオブジェクトをコピーする機能を提供してくれる標準ライブラリの copy モジュールのソースコードの中にimmutable なクラスを列挙したと思われるものがありました。

ただ、ここで列挙されたものが全て immutable である保証もなく、ほかにも組み込み型で immutable であるものがあるかもしれません。 これも、こんなものがあるんだなーくらいに眺めておいていただければ幸いです。

まずは、とりあえず、よく使うものだけ覚えておけば、いいのではないでしょうか。

  • int
  • float
  • str
  • tuple
  • bool
  • range
  • type(None)
  • type
  • types.BuiltinFunctionType
  • types.FunctionType *これは mutable, 後述します。

その他にもこんなのがあります。

  • bytes
  • complex
  • frozenset
  • slice
  • type(Ellipsis)
  • type(NotImplemented)
  • weakref.ref

# 4.1. よく知らないのも入ってない?

見慣れないものも入っていますが type はユーザ定義クラスと組み込み型の型、 types.BuiltinFunctionType はユーザ定義関数の型、types.FunctionType は組み込み関数の型になります。

import types

class Cls:
    pass

def f():
    pass


# 1. 組み込み型
isinstance(int, type)  # True

# 2. ユーザ定義クラス
isinstance(Cls, type)  # True

# 3. 組み込み関数
isinstance(max, types.BuiltinFunctionType)  # True

# 4. ユーザ定義関数
isinstance(f, types.FunctionType)  # True

types
このモジュールは(types は)、 Python インタプリタを実装するために必要な多くの型に対して名前を提供します。

isinstance
object 引数が classinfo 引数のインスタンスであるか、 (直接、間接、または 仮想) サブクラスのインスタンスの場合に真を返します。 object が与えられた型のオブジェクトでない場合、この関数は常に偽を返します。

weakref.ref は、また別の機会にご紹介させてください。 これはガベレージコレクションとか説明しないといけないので、すこし重いので。

# 4.2. 根拠は、どこにあるの?

公式ドキュメント内では見つからず、 標準ライブラリ copy の中に immutable な型を列挙していると思われる箇所がありました。 そこから引張ています。

_copy_dispatch = d = {}

def _copy_immutable(x):
    return x
for t in (type(None), int, float, bool, complex, str, tuple,
          bytes, frozenset, type, range, slice,
          types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
          types.FunctionType, weakref.ref):
    d[t] = _copy_immutable
t = getattr(types, "CodeType", None)
if t is not None:
    d[t] = _copy_immutable

d[list] = list.copy
d[dict] = dict.copy
d[set] = set.copy
d[bytearray] = bytearray.copy

if PyStringMap is not None:
    d[PyStringMap] = PyStringMap.copy

del d, t

本当にそうなの?って感じですが、 copy.py のコードの詳細については、この先の以下の記事でご紹介させていただきたいと考えております。

# 4.3. 2つの PEP

immutable なクラスに関連した2つの PEP をご紹介します。 ここは特に適当に流し読みしてください。

# (1) types.FunctionType - def を使って定義した関数は mutable

types.FunctionType は def を使って定義した関数のクラスです。 types.FunctionType は、ちゃんと確認してみると mutable でした。 PEP 232 で Function Attributes として認められ Python 2.1 から types.FunctionType は mutable になったそうです。

def f():
    return f.a

f.a = 10
f()  # 10
f.a = 20
f()  # 20

クラスでラップしてしまえばよかったんじゃないんやろか。 こんなときこそ classmethod の使いどころかな、と思ったのですが...

class C:
    @classmethod
    def f(cls):
        return cls.a
    
    def g():
        return C.a

C.a = 10
C.f()  # 10
C.a = 20
C.g()  # 20

classmethod を使って名前空間 cls を明示した方が Python らしい書き方かなと思ったりします。 なんでこれだけ孫悟空みたいな口調の訳になってるんだろう笑

名前空間ってのは、すんげーアイデアなんだなぁ。これ、もっと使っていこうよ!
Namespaces are one honking great idea -- let's do more of those!
The Zen of Python - PEP 20

と思ったら PEP の中にあったメールへのリンクで、ちゃんと説明されている様子。 詳細はちゃんとまだ読みきっていない。要約すると、いちいち関数をクラスで wrap するなんて、面倒くさいやろバーローってことらしい。

関数とメソッドへの任意属性 - Python Dev
Arbitrary attributes on funcs and methods - Python-Dev

クラスインスタンスと比べて何が利点ですか?
What are the benefits compared to class instances?

もし私が関数には属性を持たせないというあなたの考えに従うなら、 関数と関連のあるオブジェクトを扱いたいときは、いつも関数とそのオブジェクトをクラスでラップしないといけなくなる。
If I follow you, you are saying that whenever you need to associate information with a function, you should wrap up the function and object into a class.

しかし、そのラップしたことによる結果は、すべての個々の関数がクラスとなるようなプログラムを生み出すことになる。 そんなことは信じられないくらい面倒だ、特に Python のスコープのルールにおいては。一般に、おそらく可能でさえない。
But the end result of this transformation could be a program in which every single function is a class. That would be incredibly annoying, especially with Python's scoping rules. In general, it may not even be possible.

読めてもいないけど、個人的には新しい機能は導入して欲しくなかったかな。そんな特別なルール、実装が必要だったのだろうかと疑問に感じたりもします。

ルールを破ってまで作るべき特例なんてない
Special cases aren't special enough to break the rules.
The Zen of Python - PEP 20

クラスは、スコープを与えてくれます。彼らが欲しがっている機能はまさにこれです。 この関数に属性を持たせると言う機能が善しとされるなら、関数がクロージャでもないのに状態、簡単に言えば属性を持ってしまうことになります。

属性を持ってしまうということは、すなわち副作用を持ってしまうということです。 関数をクラスに属させれば、属性を持つ特別な関数であることを明示することができます。 副作用を持つことを明示するために、この2行追加することは妥当じゃないかなと感じたりもします。

「暗黙」よりも「明示」
Explicit is better than implicit.
The Zen of Python - PEP 20

となると、copy モジュールで変数名に immutable と書くのは、ええのやろかとも思ったけど。 singleton として扱ってるなら問題ないんやろな。 それなら _copy_immutable じゃなくて _copy_as_singleton の方が関数の表現としては適切なのではないやろか.. それはそれで、わかりづらいか。

# (2) frozendicit - immutable な dict は採用されなかった

ちなみに immutable な dict として、frozendict というのものが PEP 416 で提案されたそうですが。 reject されたようです。なんで tuple, frozenset は組み込み型で、namedtuple は標準ライブラリで実装されているのに frozendict は完全に不採用なんだろう。

PEP 416 - 組み込み型への forzendict の追加

(意訳) 却下通知 Raymond Hettinger によると、frozendict を実装する必要性は低い。 ここにいる人たちは、モジュールもしくはクラスの変数が定数であることを示唆するためだけに、 frozendict を導入したいと考えているようだ。 しかし、frozendict が代入された変数は、実際には定数ではない。 Python では、変数に別のオブジェクトを代入することができるからである。

(直訳) 却下通知 Raymond Hettinger によると、frozendict の使用は低い。 forzendict を使うのは、ヒントのためだけに使われる傾向がある。 例えば global もしくは class レベルの定数を宣言する。 これらは実際には immutable ではない、誰でも名前に代入することができるからである。

(原文) Rejection Notice According to Raymond Hettinger, use of frozendict is low. Those that do use it tend to use it as a hint only, such as declaring global or class-level "constants": they aren't really immutable, since anyone can still assign to the name.

# 5. イミュータブルの判定

かなり、難しい... 多分できない。

いちいち immutable なオブジェクトを覚えるなんて面倒ですよね。 だから、isimmutable(obj) みたいな感じで、判定できたら理解もしやすそうです。

ただ Python ではオブジェクトを immutable にする機能があるわけではなさそうです。 immutable とはオブジェクトの属性を変更できないという、 オブジェクトの性質を表す言葉でしかありません。

オブジェクトの性質を調べるために、単純に属性に力技で代入して例外が発生したら immutable であるかどうか判断するような関数であれば それらしいものは作れそうな気もするのですが... ほとんど immutable で、どこか一箇所の属性だけ mutable な場合は力技では判定できません。

おまけに Python には、属性参照をカスタマイズすることができるディスクリプタという機能があります。 これを使われると、そのような力技の実装による判定では手出しすることさえできなくなります。 namedtuple が実装で使っているとご紹介させていただいた property もディスクリプタの仲間です。 ディスクリプタについては Effective Python の 4 章を読むとわかりやすいです(理解したとは言っていない)。

以下のメソッドを定義して、クラスインスタンスへの属性値アクセス ( 属性値の使用、属性値への代入、 x.name の削除) の意味をカスタマイズすることができます。
3.3.2. 属性値アクセスをカスタマイズする - Python 言語リファレンス

# 6. 定数

None, Flase, True は Python は定数です。定数とは代入できない変数ということです。 定数を作る機能があるならオブジェクトを immutable にしたり mutable に切り替えるのも簡単に実装できそう気がします。 しかし、これをどうやってこれを実装しているのでしょうか。

None = 1
>>> None = 1
  File "<stdin>", line 1
SyntaxError: can't assign to keyword
>>> 

class, def, if, for などと同じ 予約語 として定義することによって定数を実装しています。 ざっくり言えば、特例的に None, False, True だけ定数にしていて、簡単には他の変数には適用できないと言うことです。 オブジェクトを immutable にしたり mutable に切り替える機能は、簡単に実装できそうにもないと言うわけです。

# 構文エラー SyntaxError なので関数も実行する前に
# 定義した段階でエラーで弾かれる
def f():
    None = 1
>>> def f():
...   None = 1
... 
  File "<stdin>", line 2
SyntaxError: can't assign to keyword
>>> 

# 7. まとめ

mutable であるか immutable であるかは型、クラスごとに決まります。 ソースコードを覗いて個別に判断するほかなさそうです。 結局 Python は 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.1. Objects, values and types - The Python Language Reference

Last Updated: 11/18/2019, 4:41:48 PM