python对象赋值、浅复制、深复制的区别
前言
与任何编程语言一样,python的对象是存放在某个内存块当中的。python的id函数的作用是求对象的内存地址。例如,
a = 1
id(a)
1727064528
或
a = 'hello'
id(a)
2336504646432
python有“一切皆对象”的口号。在python中,哪怕是在别的面向对象语言中被定义为基本数据类型的变量在python中也是一个对象(这点与java中保留了int, float等基本数据类型不同)。一个有趣的现象是,python中给同一个变量名的变量两次赋不同的常数值,变量的id是不同的。
a = 0
id(a)
1727064496
a = 1
id(a)
1727064528
而且可以注意到,同样的数值,不论变量名是什么,对应的id是一样的。
这与C/C++对int变量的处理截然不同。在C/C++中,对一个int变量赋不同的值,该变量的地址是不会变化的。
#include<iostream>
using namespace std;
int main()
{
int a = 0;
cout << &a << endl; // 0x6dfefc
a = 1;
cout << &a << endl; // 0x6dfefc
return 0;
}
python中这种对于数值常量的处理与java对于字符串常量类似。
public class Main
{
public static void main(String args[])
{
String s1 = "hello";
System.out.println(System.identityHashCode(s1)); // 366712642
s1 = "world";
System.out.println(System.identityHashCode(s1)); // 1829164700
}
}
赋值
python中的赋值 x = y 是对对象引用的赋值,并没有产生新的对象。赋值之后引用x与y指向同一个对象,一切对x的修改,对y同样生效,反之亦然。图解如下,
浅复制
与赋值不同,对象的浅复制会构造新的对象,并把原对象内存地址中的内容复制到新对象中,是一种”按比特的复制”。“按比特”的意思是,浅复制得到的新对象的内容与原对象是完全一样的,如果原对象的A属性值为a,则新对象的A属性值也是a。
但是,正如其名字所揭示的那样,浅复制不能实现对对象的完全复制。问题出在当对象的属性包含对象的引用时。下面代码中的Class类的属性含有对Stu类对象的引用,所以Class对象就属于“包含对象引用的对象”。
class Stu:
def __init__(self, sid):
self.sid = sid
def set_id(self, sid):
self.sid = sid
def get_id(self):
return self.sid
def set_name(self, name):
self.name = name
def get_name(self):
return self.name
class Class:
def __init__(self):
self.stu_num = 50
self.monitor = Stu(1)
self.monitor.set_name("XiaoHong")
def set_stu_num(self, num):
self.stu_num = num
def set_monitor(self, mid, mname):
self.monitor.set_id(mid)
self.monitor.set_name(mname)
def display(self):
print(f'stu_num: {self.stu_num}; monitor: {self.monitor.get_id()}, {self.monitor.get_name()}')
如上文所述,浅复制对于对象中的内容会“逐比特”拷贝,那么对象中的引用(monitor)也被直接拷贝了,即是说,对于对象中的属性对象(Stu monitor),浅复制进行了类似直接赋值的操作,并没有新的属性对象被构造,原对象和新对象共享同一个属性对象(Stu monitor)。因此,当原对象的属性对象发生变化时,新对象的属性对象也随之发生变化:这种变化很多时候是我们所不期望的。
图解如下,
python中复制对象需要导入模块
import copy
浅复制的代码是
y = copy.copy(x)
深复制
与浅复制不同,遇到对象内部的属性对象时,深复制会递归地构造新对象并复制属性对象的内容,而非简单地复制属性对象的引用。因此,深复制可以解决浅复制共享属性对象的问题。图解如下,
深复制的python代码是
import copy
y = copy.deepcopy(x)
一个例子
下面用一个例子比较赋值、深复制、浅复制的差异
import copy
class Stu:
def __init__(self, sid):
self.sid = sid
def set_id(self, sid):
self.sid = sid
def get_id(self):
return self.sid
def set_name(self, name):
self.name = name
def get_name(self):
return self.name
class Class:
def __init__(self):
self.stu_num = 50
self.monitor = Stu(1)
self.monitor.set_name("XiaoHong")
def set_stu_num(self, num):
self.stu_num = num
def set_monitor(self, mid, mname):
self.monitor.set_id(mid)
self.monitor.set_name(mname)
def display(self):
print(f'stu_num: {self.stu_num}; monitor: {self.monitor.get_id()}, {self.monitor.get_name()}')
if __name__ == '__main__':
# assign
print('Assign')
c1 = Class()
c2 = c1
print(f'c1 id: {id(c1)}, c2 id: {id(c2)}') # id(c1) == id(c2)
c1.set_stu_num(40)
c1.set_monitor(0, 'XiaoMing')
print('c1:') # c1 EQUALS c2
c1.display()
print('c2:')
c2.display()
print()
# shallow copy
print('Shallow Copy')
c1 = Class()
c2 = copy.copy(c1)
print(f'c1 id: {id(c1)}, c2 id: {id(c2)}') # id(c1) != id(c2)
print(f'c1.monitor id: {id(c1.monitor)}, c2.monitor id: {id(c2.monitor)}') # id(c1.monitor) == id(c2.monitor)
c1.set_stu_num(40)
c1.set_monitor(0, 'XiaoMing')
print('c1:') # c1.stu_num != c2.stu_num
c1.display() # c1.monitor EQUALS c2.monitor
print('c2:')
c2.display()
print()
# deep copy
print('Deep Copy')
c1 = Class()
c2 = copy.deepcopy(c1)
print(f'c1 id: {id(c1)}, c2 id: {id(c2)}') # id(c1) != id(c2)
print(f'c1.monitor id: {id(c1.monitor)}, c2.monitor id: {id(c2.monitor)}') # id(c1.monitor) != id(c2.monitor)
c1.set_stu_num(40)
c1.set_monitor(0, 'XiaoMing')
print('c1:') # c1.stu_num != c2.stu_num
c1.display() # c1.monitor NOT EQUAL c2.monitor
print('c2:')
c2.display()
这段代码的输出如下
Assign
c1 id: 2427692355480, c2 id: 2427692355480
c1:
stu_num: 40; monitor: 0, XiaoMing
c2:
stu_num: 40; monitor: 0, XiaoMing
Shallow Copy
c1 id: 2427692355536, c2 id: 2427692442288
c1.monitor id: 2427692443296, c2.monitor id: 2427692443296
c1:
stu_num: 40; monitor: 0, XiaoMing
c2:
stu_num: 50; monitor: 0, XiaoMing
Deep Copy
c1 id: 2427692355480, c2 id: 2427692442344
c1.monitor id: 2427692355424, c2.monitor id: 2427692443240
c1:
stu_num: 40; monitor: 0, XiaoMing
c2:
stu_num: 50; monitor: 1, XiaoHong