7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Python/Rubyの浅いコピーと深いコピーについて

Last updated at Posted at 2017-09-04

#Python/Rubyの浅いコピーと深いコピーについて
PHPとは違いPythonとRubyでは代入がコピーではなく、代入は「ターゲットとオブジェクトの間に束縛を作る」ことを意味する。「ターゲットとオブジェクトの間に束縛を作る」という意味が分かりづらいかったということと、「イミュータブル」と「ミュータブル」という意味について、サンプルをベースに理解を深めるために備忘録として残した。
ここでは、Python3.6.2、Ruby2.4.1を利用した。

##代入
###整数の代入
以下のPython3.6とRuby2.4の結果から、変数aを操作するとaidが変わり、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であり、異なる値に変更することができる。すなわち、ミュータブル(変更可)である。ここでは便宜上、このようなオブジェクトをミュータブルオブジェクトとして扱う。
baの値を代入した場合、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]

##浅いコピー
ミュータブルオブジェクトの場合、上述の例のように、baの値を代入した場合、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を利用することで浅いコピーが実現できる。
なお、こちらのようにRailsActiveModel.cloneActiveModel.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.loadMarshal.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
7
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?