一、变量、对象和引用的关系

1.变量

  所有的变量必须在其使用前明确地赋值,使用未赋值的变量会产生错误,变量在赋值的时候才创建,它可以引用任何类型的对象。

>>> print(a)    #变量a未明确赋值,产生错误
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

2.引用

  在Python中从变量到对象的连接称作引用。也就是说,引用是一种关系,以内存中的指针的形式实现。一旦变量被使用(也就是说被引用),Python自动跟随这个变量到对象的连接。以具体的术语来讲:

  • 变量是一个系统级的元素,拥有指向对象的连接的空间。
  • 对象是分配的一块内存,有足够的空间去表示它们所代表的值。
  • 引用是自动形成的从变量到对象的指针。

3.对象 

  Python中一切皆为对象,不管是集合变量还是数值型or字符串型的变量都是一个引用,都指向对应内存空间中的对象。

一个对象都有两个标准的头部信息:一个类型标识符去标识这个对象的类型,以及一个引用的计数器,用来决定是不是可以回收这个对象。

 

二、赋值生成引用,而不是拷贝

1.赋值

  赋值操作总是储存对象的引用,而不是这些对象的拷贝。

a = 3
b = a
print("a的ID:",id(a))
print("b的ID:",id(b))
#相同的内存空间
输出:
a的ID: 1631576880
b的ID: 1631576880

变量a赋值给变量b,而b变量引用了与a变量相同的对象,即指向了相同的内存空间,这在Python中叫做共享引用---多个变量名引用了同一个对象。

a的内存空间地址不变),则b跟着改变(b的内存空间地址不变);同样的,改变b中的值,则a跟着改变。

a = [1,2,3]
b = a
a[1] = "spam"
print("a=",a)
print("b=",b)
print("a的ID:",id(a))
print("b的ID:",id(b))

#输出
a= [1, 'spam', 3]
b= [1, 'spam', 3]
a的ID: 38728368
b的ID: 38728368

 

  (2)当a,b是不可变对象时,b和a指向同一个内存空间,改变a中的值(a的内存空间地址改变),这时b不跟着改变(因为b的内存空间地址不变);同样的,改变b中的值,这时a不跟着改变。

a = 3
b = a
a = 1
print("a=",a)
print("b=",b)
print("a的ID:",id(a))
print("b的ID:",id(b))

#输出
a= 1
b= 3
a的ID: 1632559888
b的ID: 1632559920

 

 

2.拷贝

通过拷贝得到的新变量与原来的变量的内存空间不同。拷贝分为直接拷贝,浅拷贝,深拷贝。

(1)直接拷贝

当我们不知道是引用还是拷贝的情况下,可以显式的拷贝。

  a.字典copy方法(X.copy())能够复制字典。

D = {"a":1,"b":2}
A = D.copy()
print(A)
输出:{'a': 1, 'b': 2}

  b.没有限制条件的分片表达式(L[:])能够复制序列。

L = [1,2,3]
B = L[:]
print(B)
输出:[1, 2, 3]

  c.有些内置函数(例如:list)能够生成拷贝(list(L))

以上拷贝方法,无条件值的分片以及字典copy方法只能做浅层复制,不能够复制嵌套的数据结构,如果需要一个深层嵌套的数据结构的完整的、完全独立的拷贝,那么就要使用标准的copy模块,即浅拷贝和深拷贝。

(2)浅拷贝

  对于简单的对象(不可变),深浅拷贝都是一样的,上面的字典对象的copy方法就是浅拷贝。如果拷贝的对象中即有可变对象,又有不可变对象,则深浅拷贝两者就有区别。

(3)深拷贝

  深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,在这个过程中我们对这两个变量中的一个进行任意修改都不会影响其他变量。

3.浅拷贝与深拷贝的区别

import copy

a = [1, [1, 2, 3], [4, 5, 6]]
b = a               #赋值
c = copy.copy(a)    #浅拷贝
d = copy.deepcopy(a) #深拷贝

a.append(15)
a[1][2] = 10
a[0] = 0

print(a)
print(b)
print(c)
print(d)

print(id(a))
print(id(b))
print(id(c))
print(id(d))

 

1 #输出
2 [0, [1, 2, 10], [4, 5, 6], 15]   #a
3 [0, [1, 2, 10], [4, 5, 6], 15]  #b
4 [1, [1, 2, 10], [4, 5, 6]]    #c
5 [1, [1, 2, 3], [4, 5, 6]]     #d
6 38925536    #a的内存空间
7 38925536   #b的内存空间
8 38925496    #c的内存空间
9 38925456   #d的内存空间

 

print('a[1]',id(a[1]))
print('b[1]',id(b[1]))
print('c[1]',id(c[1]))
print('d[1]',id(d[1]))

print('a[0]',id(a[0]))
print('b[0]',id(b[0]))
print('c[0]',id(c[0]))
print('d[0]',id(d[0]))

 

#输出
a[1] 35124528
b[1] 35124528
c[1] 35124528
d[1] 35124328


a[0] 1631576832
b[0] 1631576832
c[0] 1631576848
d[0] 1631576848

   b和a指向同一个内存空间,改变a中的值,则b跟着改变。c是a浅拷贝,c指向和a不同的内存空间,但是如果a中有一个元素为可变对象,则c中的此元素和a中的元素指向同一个内存空间,则改变a中此可变对象里面的值时,c中的值也改变改变a中不可变对象的元素的值时,c中的值不发生变化。d是a的深拷贝,d和a指向不同的内存空间,d内部的元素和a内部元素也指向不同的空间,改变a里面的值时,d不会发生变化

 总结:

浅拷贝只是增加了一个指针指向一个存在的地址,而深拷贝是增加一个指针并且开辟了新的内存,这个增加的指针指向这个新的内存,

采用浅拷贝的情况,释放内存,会释放同一内存,深拷贝就不会出现释放同一内存的错误。