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

“==”和“is”

  • ==运算符比较两个对象的值(对象中保存的数据),而is比较对象的标识
# id()返回对象标识的整数表示
charles = {'name': 'Charles L. Dodgson', 'born': 1832}
lewis = charles
lewis is charles
id(charles), id(lewis)

lewis['balance'] = 950
alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
# alex的值与charles相等,但是标识不同
alex == charles
alex is not charles

在变量和单例值之间比较时,应该使用is,is运算符比==速度快。

元组的相对不可变性

  • 元组中不可变的是元素标识
t1 = (1, 2, [30, 40])
t2 = (1, 2, [30, 40])
t1 == t2
id(t1[-1])
# 元组中的列表可以变化,列表是引用
t1[-1].append(99)
t1
id(t1[-1])
# 改变后不相等
t1 == t1

浅复制、深复制

变量交互式动画

  • 浅复制:l2 = list(l1)、列表切片[:]默认为浅复制
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)
l1.append(100)
l1[1].remove(55)
# 列表是可变的,还是共享同一个序列
l2[1] += [33, 22]
# 元组不可变,自加操作会产生新的元组
l2[2] += (10, 11)

浅复制中的嵌套列表、元组为引用,浅复制的嵌套列表使用的是同一id的引用进行“+=”自加等改变列表的操作都会改变。由于元组为不可变序列,进行“+=”自加赋值运行会创建新的元组。

  • 不要使用可变类型作为参数的默认值,例如列表。
    使用列表作为参数的默认值时,如果初始化对象时传入列表,则一切正常。但是如果没有传入列表,则两个初始化对象的参数列表将是同一个列表的引用。
  • copy模块的深复制deepcopy和浅复制copy函数
class Bus:
    def __init__(self, passengers=None):
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = list(passengers)
    
    def pick(self, name):
        self.passengers.append(name)
        
    def drop(self, name):
        self.passengers.remove(name)
        
import copy
bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
# 浅复制对象
bus2 = copy.copy(bus1)
# 深复制对象
bus3 = copy.deepcopy(bus1)
# 查看内存标识id,对象id不一样
id(bus1), id(bus2), id(bus3)
bus1.dorp('Bill')
# bus1的对象列表改变了,浅复制的bus2也跟着变了
bus2.passengers
# 浅复制对象的列表属性id一样
id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)
bus3.passengers
  • 循环引用
a = [10, 20]
b = [a, 30]
a.append(b)
a
from copy import deepcopy
c = deepcopy(a)
c

函数的参数作为引用时

共享传参:指函数的各个形式参数获得实参中各个引用的副本,函数内部的形参是实参的别名。这样的结果将导致传入的可变对象可能会改变。

  • 不要使用可变类型作为参数的默认值
# 传入的列表默认是空列表
class HauntedBus:
    def __init__(self, passengers=[]):
        self.passengers = passengers
        
    def pick(self, name):
        self.passengers.append(name)
        
    def drop(self, name):
        self.passengers.remove(name)
        
bus1 = HauntedBus(['Alice', 'Bill'])
bus1.passengers
bus1. pick('Charlie')
bus1.drop('Alice')
bus1.passengers
# 使用默认的空列表创建对象,一切正常
bus2 = HauntedBus()
bus2.pick('Carrie')
bus2.passengers
# 再默认列表创建一个对象,这里就出现问题了
bus3 = HauntedBus()
# bus3 和 bus2 使用了同一个默认对象的引用
bus3.passengers
bus3.pick('Dave')
bus2.passengers
# bus2 和 bus3 的passengers 属性标签一样
bus2.passengers is bus3.passengers
bus1.passengers
  • 防御可变参数
    在传入可变参数的时候,需要明确调用方是否期望修改传入的参数。
# 不希望被修改的就创建一个新的列表
class TwilightBus:
    def __init__(self, passengers=None):
        if passengers is None:
            self.passengers = []
        else:
            # 这里的引用列表不能直接赋值,直接赋值的只是引用。需要做一次浅复制。这里使用强制转换成列表,还带来了更灵活的实现,只要传进来的是可迭代对象就可以了。
            self.passengers = list(passengers)
    
    def pick(self, name):
        self.passengers.append(name)
        
    def drop(self, name):
        self.passengers.remove(name)

del和垃圾回收

  • del语句删除名称,而不是对象。当对象的最后一个引用名称被删除后,对象就被当作垃圾回收。
    CPython立即回收使用的主要算法是引用计数。当对象的引用计数归零时,对象立即就被销毁。

不可变类型的“善意谎言”

  • 使用另一个元组构建元组,得到的是同一个元组
t1 = (1, 2, 3)
t2 = tuple(t1)
t2 is t1
t3 = t1[:]
t3 is t1
  • 字符串字面量可能会创建共享的对象
t1 = (1, 2, 3)
t3 = (1, 2, 3)
# 这里是两个不同的元组对象,不相等
t3 is t1
s1 = 'ABC'
s2 = 'ABC'
# 产生了共享的字符串对象
s2 is s1

总结

  • 首先弄清楚了“==”和“is”的区别
  • 进一步了解了元组的相对不可变性,即元组里的列表依然可变
  • 进一步掌握对象的浅复制和深复制
  • 以及要尽量避免使用可变类型作为参数的默认值,以及可变参数需要考虑要不要接受修改传进来的参数。
  • 了解了CPython的del和垃圾回收机制,当对象的引用计数为零时,对象被回收。
  • 最后了解了不可变类型的一些把戏,可以用于节省内存。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值