#Python/Rubyの浅いコピーと深いコピーについて
PHPとは違いPythonとRubyでは代入がコピーではなく、代入は「ターゲットとオブジェクトの間に束縛を作る」ことを意味する。「ターゲットとオブジェクトの間に束縛を作る」という意味が分かりづらいかったということと、「イミュータブル」と「ミュータブル」という意味について、サンプルをベースに理解を深めるために備忘録として残した。
ここでは、Python3.6.2、Ruby2.4.1を利用した。
##代入
###整数の代入
以下のPython3.6とRuby2.4の結果から、変数a
を操作するとa
のid
が変わり、3
という整数オブジェクトに対して変更できないためイミュータブル(変更不可)である。ここでは便宜上、このようなオブジェクトをイミュータブルオブジェクトとして扱う。
####Python3.6の場合
a = 3
print("id_3:%d, id_a:%d" % (id(3), id(a)))
b = a
print("id_b:%d" % id(b))
a += 1
print("id_3:%d, id_4:%d, id_a:%d, id_b:%d" % (id(3), id(4), id(a), id(b)))
id_3:1623654960, id_a:1623654960
id_b:1623654960
id_3:1623654960, id_4:1623654992, id_a:1623654992, id_b:1623654960
Python3.6の場合、下記のように同じ値の整数でもid
が異なるケースがある。
class A:
def get(self):
print(id(99999999999999999999999999999))
class B:
def get(self):
print(id(99999999999999999999999999999))
A().get()
B().get()
2812564670744
2812564671464
####Ruby2.4の場合
a = 3
puts 'id_3:%d, id_a:%d' % [3.object_id, a.object_id]
b = a
puts 'id_b:%d' % [b.object_id]
a += 1
puts 'id_3:%d, id_4:%d, id_a:%d, id_b:%d' % [3.object_id, 4.object_id, a.object_id, b.object_id]
id_3:7, id_a:7
id_b:7
id_3:7, id_4:9, id_a:9, id_b:7
なお、Rubyの場合でも、下記のように同じ値の整数でもid
が異なるケースがある。
p 99999999999999999999999.object_id
p 99999999999999999999999.object_id
25133964
25133856
###配列(Ruby)・リスト(Python)の代入
以下のPython3.6とRuby2.4の結果から、a
を操作して値が変更しても同じid
であり、異なる値に変更することができる。すなわち、ミュータブル(変更可)である。ここでは便宜上、このようなオブジェクトをミュータブルオブジェクトとして扱う。
b
にa
の値を代入した場合、id
が同じため、a
を操作することにより値が変更された場合、b
も変わる。
####Python3.6の場合
a = [1,2,3]
b = a
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
a[1] = 4
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
id_a:4332032456, id_b:4332032456
[1, 2, 3] [1, 2, 3]
id_a:4332032456, id_b:4332032456
[1, 4, 3] [1, 4, 3]
####Ruby2.4の場合
a = [1,2,3]
b = a
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
p a, b
a[1] = 4
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
p a, b
id_a:70131497645860, id_b:70131497645860
[1, 2, 3]
[1, 2, 3]
id_a:70131497645860, id_b:70131497645860
[1, 4, 3]
[1, 4, 3]
##浅いコピー
ミュータブルオブジェクトの場合、上述の例のように、b
にa
の値を代入した場合、id
が同じため、a
を操作することにより値が変更された場合、b
も変わってしまう。b
の値をa
と独立させるにはコピーをする必要がある。この例でのリストや配列の場合、浅いコピーを施すことで実現できる。
下記の例では浅いコピーをすることで、a
の操作で値が変更されても、b
の値は変わらない。
###Python3.6の場合
copy.copy
関数またはa[:]
を代入することで浅いコピーができる。
import copy as cp
a = [1,2,3]
b = cp.copy(a)
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
a[1] = 4
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
b = a[:]
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
a[1] = 3
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
id_a:4460690760, id_b:4460690888
[1, 2, 3] [1, 2, 3]
id_a:4460690760, id_b:4460690888
[1, 4, 3] [1, 2, 3]
id_a:4460690760, id_b:4460691272
[1, 4, 3] [1, 4, 3]
id_a:4460690760, id_b:4460691272
[1, 3, 3] [1, 4, 3]
###Ruby2.4の場合
Object.clone
またはObject.dup
を利用することで浅いコピーが実現できる。
なお、こちらのようにRails
のActiveModel.clone
とActiveModel.dup
と挙動が異なる。
a = [1,2,3]
b = a.clone
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
p a, b
a[1] = 4
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
p a, b
b = a.dup
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
p a, b
a[1] = 3
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
p a, b
id_a:70245067666580, id_b:70245067666520
[1, 2, 3]
[1, 2, 3]
id_a:70245067666580, id_b:70245067666520
[1, 4, 3]
[1, 2, 3]
id_a:70245067666580, id_b:70245067665580
[1, 4, 3]
[1, 4, 3]
id_a:70245067666580, id_b:70245067665580
[1, 3, 3]
[1, 4, 3]
###浅いコピーでは不十分となるケース
例えばリストの要素にリスト、配列の要素に配列がある場合、すなわちミュータブルオブジェクトからさらに別のミュータブルオブジェクトがあるような下記の例では、浅いコピーをしてもa[0]
を操作により、b[0]
の値が変更されてしまう。これはPython3.6もRuby2.4も同一の挙動となる。
####Python3.6の場合
import copy as cp
a = [[1,2,3], "abc"]
b = cp.copy(a)
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
a[0][1] = 4
a[1] = "def"
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
b = a[:]
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
a[0][1] = 3
a[1] = "abc"
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
id_a:4393106888, id_b:4393107272
id_a[0]:4393106760, id_b[0]:4393106760
id_a[1]:4391239728, id_b[1]:4391239728
[[1, 2, 3], 'abc'] [[1, 2, 3], 'abc']
id_a:4393106888, id_b:4393107272
id_a[0]:4393106760, id_b[0]:4393106760
id_a[1]:4392739984, id_b[1]:4391239728
[[1, 4, 3], 'def'] [[1, 4, 3], 'abc']
id_a:4393106888, id_b:4393112648
id_a[0]:4393106760, id_b[0]:4393106760
id_a[1]:4392739984, id_b[1]:4392739984
[[1, 4, 3], 'def'] [[1, 4, 3], 'def']
id_a:4393106888, id_b:4393112648
id_a[0]:4393106760, id_b[0]:4393106760
id_a[1]:4391239728, id_b[1]:4392739984
[[1, 3, 3], 'abc'] [[1, 3, 3], 'def']
####Ruby2.4の場合
a = [[1,2,3], "abc"]
b = a.clone
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
puts 'id_a[0]:%d, id_b[0]:%d' % [a[0].object_id, b[0].object_id]
puts 'id_a[1]:%d, id_b[1]:%d' % [a[1].object_id, b[1].object_id]
p a, b
a[0][1] = 4
a[1] = "def"
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
puts 'id_a[0]:%d, id_b[0]:%d' % [a[0].object_id, b[0].object_id]
puts 'id_a[1]:%d, id_b[1]:%d' % [a[1].object_id, b[1].object_id]
p a, b
b = a.dup
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
puts 'id_a[0]:%d, id_b[0]:%d' % [a[0].object_id, b[0].object_id]
puts 'id_a[1]:%d, id_b[1]:%d' % [a[1].object_id, b[1].object_id]
p a, b
a[0][1] = 3
a[1] = "abc"
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
puts 'id_a[0]:%d, id_b[0]:%d' % [a[0].object_id, b[0].object_id]
puts 'id_a[1]:%d, id_b[1]:%d' % [a[1].object_id, b[1].object_id]
p a, b
id_a:70199714913920, id_b:70199714913860
id_a[0]:70199714913960, id_b[0]:70199714913960
id_a[1]:70199714913940, id_b[1]:70199714913940
[[1, 2, 3], "abc"]
[[1, 2, 3], "abc"]
id_a:70199714913920, id_b:70199714913860
id_a[0]:70199714913960, id_b[0]:70199714913960
id_a[1]:70199714912900, id_b[1]:70199714913940
[[1, 4, 3], "def"]
[[1, 4, 3], "abc"]
id_a:70199714913920, id_b:70199714912200
id_a[0]:70199714913960, id_b[0]:70199714913960
id_a[1]:70199714912900, id_b[1]:70199714912900
[[1, 4, 3], "def"]
[[1, 4, 3], "def"]
id_a:70199714913920, id_b:70199714912200
id_a[0]:70199714913960, id_b[0]:70199714913960
id_a[1]:70199714911480, id_b[1]:70199714912900
[[1, 3, 3], "abc"]
[[1, 3, 3], "def"]
##深いコピー
「浅いコピーでは不十分なケース」となるミュータブルオブジェクトが別のミュータブルオブジェクトを保持するような場合、b
の値をa
と完全にコピーさせるには浅いコピーではなく深いコピーをする必要がある。
###Python3.6の場合
copy.deepcopy
で深いコピーが実現できる。
import copy as cp
a = [[1,2,3], "abc"]
b = cp.deepcopy(a)
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
a[0][1] = 4
a[1] = "def"
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
id_a:4306767304, id_b:4306767688
id_a[0]:4306767176, id_b[0]:4306773064
id_a[1]:4304900144, id_b[1]:4304900144
[[1, 2, 3], 'abc'] [[1, 2, 3], 'abc']
id_a:4306767304, id_b:4306767688
id_a[0]:4306767176, id_b[0]:4306773064
id_a[1]:4306400400, id_b[1]:4304900144
[[1, 4, 3], 'def'] [[1, 2, 3], 'abc']
###Ruby2.4の場合
Marshal.load
とMarshal.dump
で深いコピーが実現できる。
a = [[1,2,3], "abc"]
b = Marshal.load(Marshal.dump(a))
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
puts 'id_a[0]:%d, id_b[0]:%d' % [a[0].object_id, b[0].object_id]
puts 'id_a[1]:%d, id_b[1]:%d' % [a[1].object_id, b[1].object_id]
p a, b
a[0][1] = 4
a[1] = "def"
puts 'id_a:%d, id_b:%d' % [a.object_id, b.object_id]
puts 'id_a[0]:%d, id_b[0]:%d' % [a[0].object_id, b[0].object_id]
puts 'id_a[1]:%d, id_b[1]:%d' % [a[1].object_id, b[1].object_id]
p a, b
id_a:70274700700880, id_b:70274700700800
id_a[0]:70274700700940, id_b[0]:70274700700780
id_a[1]:70274700700900, id_b[1]:70274700700760
[[1, 2, 3], "abc"]
[[1, 2, 3], "abc"]
id_a:70274700700880, id_b:70274700700800
id_a[0]:70274700700940, id_b[0]:70274700700780
id_a[1]:70274700699900, id_b[1]:70274700700760
[[1, 4, 3], "def"]
[[1, 2, 3], "abc"]
##補足
###Rubyでの文字列について
Ruby2.4では下記のように文字列リテラルはミュータブルオブジェクトとなる。こちらを参照する限りでは、文字列リテラルはRuby3でイミュータブルオブジェクトになるとのこと。
なお、Python3.6では、下記のように扱うにはb = list(a)
とリスト型に変換する必要がある。
a = "abc"
puts 'id_a:%d' % [a.object_id]
a[0] = "A"
puts 'id_a:%d' % [a.object_id]
id_a:70186456022160
id_a:70186456022160
###PHPの参照による代入について
PHPの場合、代入が深いコピーに相当するだろうか。なお、下記例では、$a = [5];
としても、$b
は$a
を参照しているため、$a
の参照する値となる。このようなPHPの$a = &$b;
のような参照による代入はPythonやRubyでは実現できない。
<?php
$a = [3];
$b = &$a;
$a = [5];
print_r($a);
print_r($b);
Array
(
[0] => 5
)
Array
(
[0] => 5
)
###浅いコピーと深いコピーについて
ここでの例ではリスト、配列を例にしたが、下記のようなケースでも浅いコピーと深いコピーの違いが判別できる。
####Python3.6の場合
import copy
class Test1:
def __init__(self):
self.a = 0
def set(self, n):
self.a = n
def get(self):
print(self.a)
class Test2:
def __init__(self, t):
self.t1 = t
def set(self, n):
self.t1.set(n)
def get(self):
self.t1.get()
a = Test2(Test1())
b = copy.copy(a)
c = copy.deepcopy(a)
a.set(3)
b.set(5)
c.set(7)
a.get()
b.get()
c.get()
5
5
7
####Ruby2.4の場合
class Test1
def initialize
@a = 0
end
def set(n)
@a = n
end
def get()
puts @a
end
end
class Test2
def initialize(t1)
@t1 = t1
end
def set(n)
@t1.set(n)
end
def get()
@t1.get
end
end
a = Test2.new(Test1.new)
b = a.clone
c = Marshal.load(Marshal.dump(a))
a.set(3)
b.set(5)
c.set(7)
a.get
b.get
c.get
5
5
7