Python —— 深拷贝 VS 浅拷贝

1. 对象的赋值:

(1)代码:

A = ['nupt',56,['I','you','he']]
B = A   # 把A赋值给B

#  比较A和B的值
print A
>>>['nupt',56,['I','you','he']]
print B    
>>>['nupt',56,['I','you','he']]     # B的值与A相同

#  比较A和B所在的地址
print id(A)   
>>>58890248
print id(B)   
>>>58890248   # B的地址与A相同

#  比较A中的每个元素与B中每个元素的地址
print [id(ele) for ele in A]
>>>[59288352L,57107912L,58888776L]
print [id(ele) for ele in B]
>>>[59288352L,57107912L,58888776L]   # B中每个元素所在地址与A相同

# 更改A的值,看B的各项变化
A[0] = 'NanJing'
A[2].append('she')

#  比较A和B的值
print A
>>>['NanJing',56,['I','you','he','she']]
print B    
>>>['NanJing',56,['I','you','he','she']]     # B的值仍与A相同

#  比较A和B所在的地址
print id(A)   
>>>58890248   # A的地址不变
print id(B)   
>>>58890248   # B的地址仍与A相同

#  比较A中的每个元素与B中每个元素的地址
print [id(ele) for ele in A]
>>>[59285712L,57107912L,58888776L]   # A[0]地址变了,A[2]没变
print [id(ele) for ele in B]
>>>[59285712L,57107912L,58888776L]   # B中每个元素所在地址与A相同

(2)代码分析:

  1. 首先,创建了一个名为A的变量,这个变量指向一个list对象,变量A及其列表元素所存储的地址如下图:
    Python —— 深拷贝 VS 浅拷贝_第1张图片
  2. 然后,通过A向B赋值,那么B将指向A的对象(内存地址),即:B is A, B[ele] is A[ele],变量B及其列表元素的地址如下图:
    Python —— 深拷贝 VS 浅拷贝_第2张图片
  3. 接着,变量A的元素发生改变。
    A[0]=’NanJing’,A[0]是str类型变量,str是不可变类型,所以修改时会产生一个新对象替换旧对象,也就相应产生一个新地址;
    A[2].append(‘she’),A[2]是list类型变量,list是可变类型,修改时不会产生新对象,故list本身内存地址不变:
    Python —— 深拷贝 VS 浅拷贝_第3张图片
  4. 由于A和B指向同一个对象,所以对A的任何修改都会体现在B上:
    A更新后B的地址

下面来看看

2. 浅拷贝:copy.copy( )

(1)代码:

import copy     # 导入copy模块
A = ['njupt',56,['I','you','he']]
B = copy.copy(A)    # 把A浅拷贝给B

#  比较A和B的值
print A
>>>['njupt',56,['I','you','he']]
print B    
>>>['njupt',56,['I','you','he']]     # 浅拷贝的B,值与A相同

#  比较A和B所在的地址
print id(A)   
>>>58890824
print id(B)   
>>>59328584      # 浅拷贝的B,地址与A不同

#  比较A中的每个元素与B中每个元素的地址
print [id(ele) for ele in A]
>>>[59288312L,57107912L,58219080L]
print [id(ele) for ele in B]
>>>[59288312L,57107912L,58219080L]   # 浅拷贝的B,元素所在地址与A相同

# 更改A的值,看B的各项变化
A[0] = 'NanJing'
A[2].append('she')

#  比较A和B的值
print A
>>>['NanJing',56,['I','you','he','she']]
print B    
>>>['njupt',56,['I','you','he','she']]    # 注意:浅拷贝的B,值与A不同

#  比较A和B所在的地址
print id(A)   
>>>58890824   # A的地址不变
print id(B)   
>>>59328584   # 浅拷贝的B,地址不变,仍与A不同

#  比较A中的每个元素与B中每个元素的地址
print [id(ele) for ele in A]
>>>[59286912L,57107912L,58219080L]   # A[0]地址变了,A[2]没变
print [id(ele) for ele in B]
>>>[59288312L,57107912L,58219080L]   # B中每个元素所在地址与A相同

(2)代码分析:

  1. 首先,创建了一个名为A的变量,这个变量指向一个list对象,变量A及其列表元素所存储的地址如下图:
    Python —— 深拷贝 VS 浅拷贝_第4张图片
  2. 然后,对A指向的对象进行浅拷贝,浅拷贝会创建一个新对象赋值给B,那么B指向的对象则不是A所指的对象,即:B is not A;但对于对象内部的元素,浅拷贝会使用原始元素的引用(内存地址),即:B[ele] is A[ele]
    Python —— 深拷贝 VS 浅拷贝_第5张图片
  3. 接着,变量A的元素发生改变。
    A[0]=’NanJing’,A[0]是str类型变量,str是不可变类型,所以修改时会产生一个新对象替换旧对象,也就相应产生一个新地址;
    A[2].append(‘she’),A[2]是list类型变量,list是可变类型,修改时不会产生新对象,故list本身内存地址不变:
    Python —— 深拷贝 VS 浅拷贝_第6张图片
  4. 由于B[2]和A[2]指向的同一地址,若存储在此地址的内容有变化,则A[2]和B[2]的内容皆会改变。因此当A[2]更新后(地址不变),B[2]的内容也随之改变;
    而A[0]更新后,其旧对象被新对象替代,即新地址替代了旧地址,而B[0]指向的旧地址,其存储的内容并未改变,因此B[0]不变:
    [A更新后,浅拷贝后B的地址

再来看看

3. 深拷贝copy.deepcopy( ):

(1)代码

import copy     # 导入copy模块
A = ['nupt',56,['I','you','he']]
B = copy.deepcopy(A)    # 把A浅拷贝给B

#  比较A和B的值
print A
>>>['nupt',56,['I','you','he']]
print B    
>>>['nupt',56,['I','you','he']]     # 深拷贝的B,值与A相同

#  比较A和B所在的地址
print id(A)   
>>>58888776
print id(B)   
>>>58890824     # 深拷贝的B,地址与A不同

#  比较A中的每个元素与B中每个元素的地址
print [id(ele) for ele in A]
>>>[59288312L,57107912L,58890248L]
print [id(ele) for ele in B]
>>>[59288312L,57107912L,59327880L]
                               # 深拷贝的B,元素所在地址与A不完全相同

# 更改A的值,看B的各项变化
A[0] = 'NanJing'
A[2].append('she')

#  比较A和B的值
print A
>>>['NanJing',56,['I','you','he' ]]
print B    
>>>['njupt',56,['I','you','he']]    # 注意:深拷贝的B,值与A不同

#  比较A和B所在的地址
print id(A)   
>>>58888776   # A的地址不变
print id(B)   
>>>58890824    # 深拷贝的B,地址不变,仍与A不同

#  比较A中的每个元素与B中每个元素的地址
print [id(ele) for ele in A]
>>>[59288592L,57107912L,58890248L]   # A[0]地址变了,A[2]没变
print [id(ele) for ele in B]
>>>[59288312L,57107912L,59327880L]   # B中每个元素所在地址不变

(2)代码分析

  1. 首先,创建了一个名为A的变量,这个变量指向一个list对象,变量A及其列表元素所存储的地址如下图:
    Python —— 深拷贝 VS 浅拷贝_第7张图片
  2. 然后,对A指向的对象进行深拷贝,深拷贝会创建一个新对象赋值给B,那么B指向的对象则不是A所指的对象,即:B is not A
    但是深拷贝跟浅拷贝的区别在于:对于对象中的元素,深拷贝都会重新生成一份(有特殊情况,下面会说明),而不是简单的使用原始元素的引用(内存地址)。
    比如例子中,B[0] != A[0],B[1]=A[1],B[2]!=A[2]
    Python —— 深拷贝 VS 浅拷贝_第8张图片
  3. 接着,变量A的元素发生改变。
    A[0]=’NanJing’,A[0]是str类型变量,str是不可变类型,所以修改时会产生一个新对象替换旧对象,也就相应产生一个新地址,id(A[0])改变
    A[2].append(‘she’),A[2]是list类型变量,list是可变类型,修改时不会产生新对象,故list本身内存地址不变,即id(A[2])不变:
    Python —— 深拷贝 VS 浅拷贝_第9张图片
  4. 但是:由于B[0] != A[0],B[2]!=A[2],so A[0]A[2]的改变对B无影响
    Python —— 深拷贝 VS 浅拷贝_第10张图片

4. 注意:

  1. Python中,类型属于对象。如a=23,变量a不是整数型,a仅仅是一个对象的引用(相当于指针),a可以指向list型,可以指向str型。

  2. 可变类型的对象:整数、字符串、元组。
    如:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,而是相当于新生成一个a

  3. 不可变类型对象: 列表,字典。
    如:变量赋值 b=[1,2,3,4] 后再赋值 b[2]=5 则是将 list b的第三个元素值更改,b本身没有动,只是其内部的一部分值被修改

5. 结论:

  1. 对于不可变类型的对象:没有拷贝一说。如:浅拷贝/深拷贝时,B[0]=A[0],B[1]=A[1],A[0]A[1]改变,B[0]B[1]都不变
  2. 对于可变类型的对象:
    (1)浅拷贝——指向原对象;原对象内容改变,拷贝对象内容也改变
    (2)深拷贝——不指向研对象,重新生成新对象然后赋值给拷贝对象;原对象内容改变,拷贝对象内容不改变

(1)B=A:
B与A值一样,指向的地址一样,内部元素各自地址一样。即:B is A, B[ele] is A[ele]
A的一切元素变,B也变

(2)B=copy.copy(A)
B与A值一样,指向的地址不一样,内部各元素地址一样。即:B is not A, B[ele] is A[ele]
A的“可变类型对象”变,B也变

(3)B=deepcopy(A)
B与A值一样,指向的地址不一样,内部元素地址可能不一样。即:B is not A, B[ele] is not A[ele]
A的“可变类型对象”变,B不变

你可能感兴趣的:(python)