理解Python中的引用,然后理解浅拷贝和深拷贝,python,进而,copy


python中的引用

  • 首先,Python中一切事物皆对象,变量是对对象在内存中的存储和地址的抽象。所有的变量都可以理解是内存中一个对象的“引用”,或者,也可以看似c中void*的感觉。
  • python中统一都是引用传递,同时要注意类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象。

1."不可更改"的对象

  • 当我们写下面语句时:
a = "hello world"

Python解释器其实顺序干了两件事情:

  • 在内存中创建一个字符串“hello world”;
  • 在内存中创建一个名为“a”的变量,并将“a”指向字符串“hello world”(将“hello world”的地址保存到“a”中)。这样我们就能通过操作“a”而改变内存中的“hello world”。
a = "123"
b = a
print(id(a),id(b))
a = "xyz"
print("a:",a,"b:",b)
print(id(a),id(b))

输出结果如下:

2450837835200 2450837835200
a: xyz b: 123
2450838051952 2450837835200
  • 执行第一句Python解释器创建字符串“123”和变量“a”,并把“a”指向“123”。
  • 执行第二句,因为“a”已经存在,并不会创建新的对象,但会创建变量“b”,并把“b”指向“a”指向的字符串“123“。 b和a同时指向“123”
  • 执行第三句,首先会创建字符串“xyz”,然后把“xyz”的地址赋予“a“(“a”指向字符串“xyz”)。

2."可更改"的对象

a = [1, 2, 3]
b = a
a[0], a[1], a[2] = 4, 5, 6 //改变原来 list 中的元素
>>> a
[4, 5, 6]
>>> b
[4, 5, 6]
  • 从这里可以看出strings类型是不可更改的对象,不可变实际上指的是不会更改字符串,比如把a = ‘123’ 变为 a =‘1234’ 实际上是又创建了 “1234” 再用a去指向它。
    但是,像list,dict等“可更改”的变量,他们会直接再本地更改,不会进行副本拷贝。
  • 简言之不可更改对象的重新赋值,当在 Python 中 a = sth 应该理解为给 sth 贴上了一个标签 a。当再赋值给 a 的时候,就好象把 a 这个标签从原来的 sth 上拿下来,贴到其他对象上,建立新的"引用"。
  • 既然Python只允许引用传递,那有没有办法可以让两个变量不再指向同一内存地址呢?
import copy
a = [1, 2, 3, 4, ['x', 'y']]#原始对象

b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝

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

a.append(5) #修改对象a
a[4].append('z') #修改对象a中的['a', 'b']数组对象

print( 'a = ', a,id(a))
print( 'b = ', b,id(b))
print( 'c = ', c,id(c))
print( 'd = ', d,id(d))
输出结果:
2450837179784 2450837179784 2450820230280 2450837973256
a =  [1, 2, 3, 4, ['x', 'y', 'z'], 5] 2450837179784
b =  [1, 2, 3, 4, ['x', 'y', 'z'], 5] 2450837179784
c =  [1, 2, 3, 4, ['x', 'y', 'z']] 2450820230280
d =  [1, 2, 3, 4, ['x', 'y']] 2450837973256
  • copy对于一个复杂对象的子对象并不会完全复制,什么是复杂对象的子对象呢?就比如序列里的嵌套序列,字典里的嵌套序列等都是复杂对象的子对象。对于子对象,python会把它当作一个公共镜像存储起来,所有对他的复制都被当成一个引用,所以说当其中一个引用将镜像改变了之后另一个引用使用镜像的时候镜像已经被改变了。
  • deepcopy的时候会将复杂对象的每一层复制一个单独的个体出来。 当然其中主要的操作还是地址问题

函数参数传递

a = 1
def fun(a):
    a = 2
fun(a)
print(a)  # 1
a = []
def fun(a):
a.append(1)
fun(a)
print(a)  # [1]

当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用没有半毛关系了.所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没半毛感觉.而第二个例子就不一样了,函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行修改.

Python垃圾回收机制中的“引用”

引用计数

  • PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
  • 优点:
    • 简单
    • 实时性
  • 缺点:
    • 维护引用计数消耗资源
    • 循环引用