# イミュータブルってなに?
# 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 (opens new window)
(イミュータブル) 固定の値を持ったオブジェクトです。イミュータブルなオブジェクトには、 数値、文字列、およびタプルなどがあります。これらのオブジェクトは値を変えられません。 別の値を記憶させる際には、新たなオブジェクトを作成しなければなりません。 イミュータブルなオブジェクトは、固定のハッシュ値が必要となる状況で重要な役割を果たします。辞書のキーがその例です。
値とはなにかは、わきに置いていおかせてください。 とりあえずいまは、変更できるオブジェクトは 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 (opens new window)
組み込み演算または関数が適切でない型のオブジェクトに対して適用された際に送出されます。 関連値は型の不整合に関して詳細を述べた文字列です。
こう言うコード見せられると「やめろっ!!」って思いますよね。 まっ、エラーになるんですけど笑 あと正直、この時の 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 として判断しても良いようです。
# ◯ 変更できる 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 (opens new window)
なんで?どうして?
なぜなら 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 (opens new window)
# ◯ 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 (opens new window)
# Step3. container object
ほぼほぼ全てのオブジェクトが複数の属性を持っているので、 ほぼほぼ全てのオブジェクトがcontainer オブジェクトだって認識でいいのではないでしょうか... int も複数の値を持ってますしね。
container - Python 言語リファレンス (opens new window) 他のオブジェクトに対する参照をもつオブジェクトもあります; これらは コンテナ (container) と呼ばれます。 コンテナオブジェクトの例として、タプル、リスト、および辞書が挙げられます。オブジェクトへの参照自体がコンテナの値の一部です。 — ワイの注記 container について記述されている箇所の抜粋しました。 タプル、リスト、および辞書など集合を表現するオブジェクトを container だと言いたい様子。 ただ、この定義だと全てのオブジェクトが container に該当してしまうんじゃまいか..
コンテナ (データ型) - Wikipedia (opens new window) コンテナとはオブジェクトの集まりを表現するデータ構造、抽象データ型またはクラスの総称である。
# 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 | 属性に直接代入されている オブジェクトを取り替えられない。 |
WARNING
ここから下は個人的な興味で、 重箱の隅をつついているので、 知らなくても困ることはありません。
# 3. イミュータブルの重要性
なんでイミュータブルは重要なのでしょうか? たかだか変更できないというだけのことに、 イミュータブルという名前までつけて有難がっているのでしょうか? 「副作用」という言葉を軸にして、以下の記事で考えてきました。
# 4. イミュータブルを実装する
Python では、可読性でも、実装面でも immutable にしても、 あまり効果がないことを上記の記事では見てきました。
ただ、immutable にするやり方は、いくつかあります。 あまり効果がなさそうので、こんなのもあるんだなーくらいに、 てきとーに眺めて流し読みして いただければと存じます。
# 4.1. collections.namedtuple 関数
標準ライブラリ collections の中にある namedtuple 関数 を用いて immutable なオブジェクトを生成するクラスを作ることができます。
from collections import namedtuple
# 1) クラス定義
Employee = namedtuple('Employee', ['name', 'id'])
# 2) インスタンス化
employee = Employee('domodomodomo', 4649)
# 3) 属性の参照
employee.name
# 4) 属性の変更(できない)
employee.name = 'hellohello'
>>> # 4) 属性の変更(できない)
... employee.name = 'hellohello'
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: can't set attribute
>>>
namedtuple の動作については、こちらで見てきました。
# 4.2. typing.NamedTuple クラス
文字列で属性を定義するなんて、面倒です。 標準ライブラリ typing の中にある NamedTuple クラス を使うともう少し簡単に書けます。 typing.NamedTuple を使えば、こんな風に書けます。
from typing import NamedTuple
# 1) クラス定義
class Employee(NamedTuple):
name: str
id: int
# 2) インスタンス化
employee = Employee('domodomodomo', 4649)
# 3) 属性の参照
employee.name
# 4) 属性の変更(できない)
employee.name = 'hellohello'
>>> # 4) 属性の変更(できない)
... employee.name = 'hellohello'
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: can't set attribute
>>>
どうやって実装しているかというと collections.namedtuple とメタクラスを組み合わせているようです。 メタクラスを使うとクラスの定義をカスタマイズできます。
# 4.3. dataclasses.dataclass クラス
Python 3.7 から標準ライブラリに dataclasses が追加されました。 標準ライブラリ dataclasses の中にある dataclass クラス を使うと typing.NamedTuple と同じような形で immutable なオブジェクトが作れます。
immutable にする場合は frozen=True を指定してください。 Python 3.6 以前でも pip install dataclasses をすれば使えます。
from dataclasse import dataclass
# 1) クラス定義
@dataclass(frozen=True)
class Employee:
name: str
id: int
# 2) インスタンス化
employee = Employee('domodomodomo', 4649)
# 3) 属性の参照
employee.name
# 4) 属性の変更(できない)
employee.name = 'hellohello'
>>> # 4) 属性の変更(できない)
... employee.name = 'hellohello'
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: can't set attribute
>>>
dataclasses の本来の使い方、説明については、ここの説明が一番わかりやすかったです。
どうやって immutable を実装しているかも覗いてみたのですが、よくわかりませんでした。
# 4.4. CPython 拡張
Cython を使って CPython(Python) そのものを拡張して immutable なオブジェクトのクラスを生成するクラスを作ることもできる様です。 よくわからないけど、すごそう...。
# ◯ 特殊属性 __slots__
immutable ではないですが __slots__ を使うと属性の追加ができないようになります。 属性の変更はできます。 使い方は簡単で __slots__ にオブジェクトが使用する変数名を list などのシーケンスで渡すだけです。
# 1) クラス定義
class Employee:
__slots__ = ['name', 'id']
def __init__(self, name, id):
self.name, self.id = name, id
# 2) インスタンス化
employee = Employee('domodomodomo', 4649)
# 3) 属性の参照
employee.name
# 4) 属性の変更(できる)
employee.name = 'hellohello'
# 5) 属性の追加(できない)
employee.gender = 'man'
>>> # 4) 属性の変更(できる)
... employee.name = 'hellohello'
>>>
>>> # 5) 属性の追加(できない)
... employee.gender = 'man'
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'Employee' object has no attribute 'gender'
>>>
__slots__ は、クラス生成の時にメモリ消費を抑えたい時に使うらしいです。 上の方に記述させていただいた結果を見ると、属性参照の速度が、20%くらい速くなります。 インスタンス化の速度は変化はありませんでした。
__slots__ (opens new window)
デフォルトでは、クラスのインスタンスは属性を保存するための辞書を持っています。 これは、ほとんどインスタンス変数を持たないオブジェクトでは領域の無駄です。 大量のインスタンスを生成するとき、この記憶領域の消費量は深刻になり得ます。
# ◯ 使い分けは?
答え: わからない...
namedtuple, NamedTuple, dataclasses の使い分けはどうすればいいのでしょうか? 正直わかりません。
namedtuple 関数は型アノテーションを使わないとき。 NamedTuple は型アノテーションを使うとき。 dataclass は型アノテーションを使うときでかつメソッドを追加しないとき。 かなと感じたりもするのですが、おそらく違います。
WARNING
ここから下は、何がイミュータブルが調べようとしました。 かなり重箱の隅をつついているので、知らなくても全く困ることはありません。
# 5. イミュータブルの一覧
公式ドキュメントで 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 (opens new window))
- type(NotImplemented)
- weakref.ref
# 5.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 (opens new window)
このモジュールは(types は)、 Python インタプリタを実装するために必要な多くの型に対して名前を提供します。
isinstance (opens new window)
object 引数が classinfo 引数のインスタンスであるか、 (直接、間接、または 仮想) サブクラスのインスタンスの場合に真を返します。 object が与えられた型のオブジェクトでない場合、この関数は常に偽を返します。
weakref.ref は、また別の機会にご紹介させてください。 これはガベレージコレクションとか説明しないといけないので、すこし重いので。
# 5.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 のコードの詳細については、この先の以下の記事でご紹介させていただきたいと考えております。
# 5.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 (opens new window) の使いどころかな、と思ったのですが...
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 (opens new window)
と思ったら PEP の中にあったメールへのリンクで、ちゃんと説明されている様子。 詳細はちゃんとまだ読みきっていない。要約すると、いちいち関数をクラスで wrap するなんて、面倒くさいやろバーローってことらしい。
関数とメソッドへの任意属性 - Python Dev (opens new window)
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 (opens new window)
クラスは、スコープを与えてくれます。彼らが欲しがっている機能はまさにこれです。 この関数に属性を持たせると言う機能が善しとされるなら、関数がクロージャでもないのに状態、簡単に言えば属性を持ってしまうことになります。
属性を持ってしまうということは、すなわち副作用を持ってしまうということです。 関数をクラスに属させれば、属性を持つ特別な関数であることを明示することができます。 副作用を持つことを明示するために、この2行追加することは妥当じゃないかなと感じたりもします。
「暗黙」よりも「明示」
Explicit is better than implicit.
The Zen of Python - PEP 20 (opens new window)
となると、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 の追加 (opens new window)
(意訳) 却下通知 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.
# 6. イミュータブルの判定
かなり、難しい... 多分できない。
いちいち immutable なオブジェクトを覚えるなんて面倒ですよね。 だから、isimmutable(obj) みたいな感じで、判定できたら理解もしやすそうです。
ただ Python ではオブジェクトを immutable にする機能があるわけではなさそうです。 immutable とはオブジェクトの属性を変更できないという、 オブジェクトの性質を表す言葉でしかありません。
オブジェクトの性質を調べるために、単純に属性に力技で代入して例外が発生したら immutable であるかどうか判断するような関数であれば それらしいものは作れそうな気もするのですが... ほとんど immutable で、どこか一箇所の属性だけ mutable な場合は力技では判定できません。
おまけに Python には、属性参照をカスタマイズすることができるディスクリプタという機能があります。 これを使われると、そのような力技の実装による判定では手出しすることさえできなくなります。 namedtuple が実装で使っているとご紹介させていただいた property もディスクリプタの仲間です。 ディスクリプタについては Effective Python の 4 章を読むとわかりやすいです(理解したとは言っていない)。
以下のメソッドを定義して、クラスインスタンスへの属性値アクセス ( 属性値の使用、属性値への代入、 x.name の削除) の意味をカスタマイズすることができます。
3.3.2. 属性値アクセスをカスタマイズする - Python 言語リファレンス (opens new window)
# 7. 定数
None, Flase, True は Python は定数です。定数とは代入できない変数ということです。 定数を作る機能があるならオブジェクトを immutable にしたり mutable に切り替えるのも簡単に実装できそう気がします。 しかし、これをどうやってこれを実装しているのでしょうか。
None = 1
>>> None = 1
File "<stdin>", line 1
SyntaxError: can't assign to keyword
>>>
class, def, if, for などと同じ 予約語 (opens new window) として定義することによって定数を実装しています。 ざっくり言えば、特例的に None, False, True だけ定数にしていて、簡単には他の変数には適用できないと言うことです。 オブジェクトを immutable にしたり mutable に切り替える機能は、簡単に実装できそうにもないと言うわけです。
# 構文エラー SyntaxError なので関数も実行する前に
# 定義した段階でエラーで弾かれる
def f():
None = 1
>>> def f():
... None = 1
...
File "<stdin>", line 2
SyntaxError: can't assign to keyword
>>>
# 8. まとめ
ここまで以下のように見てきました。
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 (opens new window)