Heavy Watal

copy

C++からプログラミングを始めて、Pythonにおけるオブジェクトの扱い、 特に代入・参照・コピー・mutable/immutableらへんの理解に苦しんでる人のメモ。

Pythonの代入は基本的に参照渡し

>>> l = [1, 2, 3]
>>> m = l
>>> l[1] = 0
>>> m
[1, 0, 3]

m に渡るのは l と同じ実体に対する参照。 l[1] = 0l を変更しているのではなく、l を通してその実体を変更してる。 その変更は m を通して見ても同じ。

>>> l = [1, 2, 3]
>>> l = m
>>> l = [6, 6, 6]
>>> m
[1, 2, 3]

l = [6, 6, 6] は新しい実体への参照を l に与える。 l が参照していた実体に対する変更ではないので、 m から見える実体にも変化は無い。

>>> x = 0
>>> y = x
>>> x = 1
>>> y
0

これも同様。y には x と同じ 0 への参照が渡る。 x = 1x を通した 0 への変更ではなく x の参照先を 1 に変えるだけなので、y の指す値に変更は無い。

mutable / immutable

mutable
list
set
dict
immutable
0, 1, 2, ...
"strings"
tuple

immutableなオブジェクトを変えることはできない。 immutableなオブジェクトを参照していた変数に別のオブジェクトの参照を渡すことはできる。

>>> t = (1, 2, 3)
>>> t[1] = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t = (0, 0, 0)
>>> t
(0, 0, 0)

文字列や数字も考え方は同じ。

>>> s = "abc"
>>> s[1] = "z"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> s = "xyz"
>>> s
'xyz'
>>> 0 = 1
  File "<stdin>", line 1
SyntaxError: can't assign to literal
>>> x = 0
>>> x = 1
>>> x
1

immutableなオブジェクトで変更できないのは「何を参照しているか」という情報。 参照先の実体がmutableなオブジェクトならそいつは変更できる。

>>> l = [1, 2, 3]
>>> t = (l, l)
>>> t
([1, 2, 3], [1, 2, 3])
>>> l[1] = 0
>>> t
([1, 0, 3], [1, 0, 3])
>>> t[0][2] = 0
>>> l
[1, 0, 0]

t が参照しているのは、l が指す実体へのimmutableな参照を持つ tuple 。 「その tuple[0] 番目で何を参照しているか」を変更することはできないが、 参照している実体はmutableな list なので lt[0] を通して変更できる。 つまり、Pythonの tuple はC++でいうところの int* const なポインタを格納した固定長配列みたいなもの?

copy

>>> import copy
>>>
>>> l = [1, 2, 3]
>>> m = copy.copy(l)
>>> l[1] = 0
>>> l
[1, 0, 3]
>>> m
[1, 2, 3]

copy.copy() によって l と同じ中身の新しい list 実体が作られ、 それへの参照が m に渡される。l を通した古い list への変更は m から見える list には影響しない。 中身がすべてimmutableな一重のコンテナならこれでいいが、 そうじゃない場合「l と同じ中身」というのが問題になる。 C++で言えば、メンバ変数としてポインタを持つクラスを デフォルトのコピーコンストラクタでコピーしたような状態になる。

>>> import copy
>>>
>>> l = [1, 2, 3]
>>> m = [l, l]
>>> x = m
>>> y = copy.copy(m)
>>> z = copy.deepcopy(m)
>>> m
[[1, 2, 3], [1, 2, 3]]
>>> m[1][1] = 0
>>> m[1] = 0
>>> x
[[1, 0, 3], 0]
>>> y
[[1, 0, 3], [1, 0, 3]]
>>> z
[[1, 2, 3], [1, 2, 3]]
>>> import copy
>>>
>>> class CopyTest(object):
...     def __init__(self):
...         self.l = [1,2,3]
...         self.s = "xyz"
...     def __repr__(self):
...         return str([self.l, self.s])
...     def self_copy(self):
...         self = copy.copy(self)
...     def self_deepcopy(self):
...         self = copy.deepcopy(self)
...     def copy(self):
...         return copy.copy(self)
...     def deepcopy(self):
...         return copy.deepcopy(self)
...
>>> a = CopyTest()
>>> b = a
>>> b.self_copy()
>>> c = a
>>> c.self_deepcopy()
>>> d = a.copy()
>>> e = a.deepcopy()
>>> a.l[1] = 0
>>> a.s = "---"
>>> a
[[1, 0, 3], '---']
>>> b
[[1, 0, 3], '---']
>>> c
[[1, 0, 3], '---']
>>> d
[[1, 0, 3], 'xyz']
>>> e
[[1, 2, 3], 'xyz']

self = copy.copy(self) なメソッドは成功しないらしい。