Python中的对象引用、可变性和垃圾回收

导语:本文章记录了本人在学习Python基础之面向对象篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。

本文重点:

1、明确变量保存的是引用这一本质;

2、熟悉对象引用的基础知识;

3、掌握深复制和浅复制;

4、熟悉函数传参引用时潜在的麻烦并避免。

一、对象引用基础知识

  1. 变量:是标注而不是容器。对引用式变量而言,是把变量分配给对象,反过来理解则不合理。
  2. 别名:同一个对象的不同标注就是别名,别名指向同一对象。
  3. 标识:可以把标识理解为对象在内存中的地址。每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变。is运算符比较两个对象的标识;id()函数返回对象标识的整数表示,即对象的内存地址。
  4. 相等性:用==运算符比较两个对象的值是否相等。注意a==b是语法糖,等同于a.__eq__(b)。
  5. ==和is的选择:我们关注值的频率比标识要高。

当比较变量和单例值的时候应该用is。例如:X is None 或 X is not None。

is运算符比==要快,因为is不能重载。

二、可变性

1、元组的相对不可变性:

指tuple数据结构的物理内容(即保存的引用)不可变。也就是说元组中不可变的是元素的标识,但元组的值会随着引用的可变对象变化而变化。
tuple,list,dict,set保存的是对象的引用,而str,byte,array.array保存的是对象的值(字符,字节,数字)。

2、浅复制与深复制

浅复制:当复制tuple,list,dict,set时,副本之间共享内部对象的引用。copy.copy()
深复制:当复制tuple,list,dict,set时,副本之间不共享内部对象的引用。copy.deepcopy()

eg:浅复制小例子

list1=[1,(55,66),[7,8,9]]
list2=list(list1)#构建副本默认为浅复制
list2[1]+=(77,88)#对元组进行+=运算会解绑list2[1],并与右端运算后的值之间重新绑定起来。
list2[2]+=[10]#对列表进行iadd运算会就地修改列表,不会发生重新绑定。
list2[0]*=3
list1[2].pop(0)
print(list1)
print(list2)
#输出:
[1, (55, 66), [8, 9, 10]]
[3, (55, 66, 77, 88), [8, 9, 10]]

分析:list2是list1的副本,我们对list2的三个元素均作了改动,但只有列表元素的改动影响到了list1。原因在于list1和list2的第三个列表元素共享引用,因此影响也会同步;元组因为发生了解绑的运算所以影响未同步到list1;至于数值的影响不同步的原因是因为浅复制针对str,byte,array.array这些对象直接将值重新保存到副本中来,不存在共享引用的内部逻辑。

深复制注意事项

  • 深复制处在循环引用的对象时,深复制算法会进入无限循环中。
  • 一些对象可能会引用不该复制的外部资源或单例值,这些对象的深复制的结果可能太深。

3、函数的参数作为引用时:

Python唯一支持的参数传递模式是共享传参。共享传参指函数的各个形式参数获得实参中各个引用的副本,即函数内部的形参是实参的别名。

  • 函数可能会修改作为参数传入的可变对象。
    (1)这个行为无法避免,除非在本地创建副本,或者使用不可变对象。

    (2)因此在类中直接把参数赋值给实例变量之前一定要三思,因为这样会为参数对象创建别名,修改传入参数指向的可变对象。

eg:函数修改作为参数传入的全局变量

def f(a, b):
    a += b
    return a
a = [1, 2]
b = [3, 4]
f(a, b)
print(a, b)#输出[1, 2, 3, 4], [3, 4],此时列表a已经发生变化。
t = (10, 20)
u = (30, 40)
f(t, u)
print(t, u)#输出((10, 20), (30, 40)),此时元组t没有发生变化。
  • 使用可变类型作为函数参数的默认值有危险。
    原因在于包含此类函数的类的实例在未指定初始值时会使用同一个可变默认值。当一个实例就地修改参数时会影响其他实例对默认值的调用。

三、垃圾回收

1、垃圾回收的判定规则:

  • 主要采用引用计数算法。
    在Python中每个对象的引用都会有统计。当引用计数归零时,对象就会立即销毁。
  • 除了循环引用外没有其他引用,处在循环引用的对象都会被销毁。

2、弱引用

  • 某些情况下可能需要保存对象的引用,但不留存对象本身,此时可以借助弱引用实现。弱引用不会妨碍对象被当做垃圾回收。
  • 弱引用是一种低层机制,是weakref模块中WeakValueDictionary、WeakKeyDictionary和WeakSet等有用的集合类,以及finalize函数的底层支持。
  • 弱引用的局限性
    弱引用所指对象可以是set,用户自定义的类,list和dict的子类。不可以是int、tuple的实例及子类,也不可以是list实例或dict实例。

四、Python对不可变类型施加的把戏

1、使用一个元组来构造另一个元组,得到的其实是同一个元组。

2、比较字符串或整数是否相等时,应该使用==而不是is。这是由于Python解释器内部驻留的特性所导致的。

    原文作者:Hanwencheng
    原文地址: https://segmentfault.com/a/1190000014244465
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞