# 第八章 对象引用、可变性、垃圾回收
"""
对象名称和对象有区别:
变量是标注,而不是盒子
内容提要:
1.对象标识、值、别名
2.元组是不可变的,但其中的值可以改变
3.深浅复制
4.引用和函数参数
可变的默认值参数引起的问题
如何安全地处理函数的调用者传入的可变参数
5.垃圾回收
del命令
使用弱引用记住对象,而无需对象本身存在
"""
# 8.1变量不是盒子
"""Python中的变量类似于Java中的引用式变量
最好把他们理解为附加在对象上的标注
所以可以给一个对象贴上多个标注,这些标注,就是别名"""
# 例 8-1 变量a,b引用同一个列表,而不是列表的副本
"""a = [1,2,3]
b = a
a.append(4)
print(a) # [1, 2, 3, 4]
print(b) # [1, 2, 3, 4]"""
# 例 8-2 创建对象之后,才会把变量分配给对象
"""class Gizmo:
def __init__(self):
print('Gizmo id:%d'%id(self))
x = Gizmo() # Gizmo id:1585308311504
# y = Gizmo()*10 # *之前会创建一个Gizmo对象
print(dir()) """
# 8.2标识/相等性/别名
"""
每个变量都有标识/类型/值
对象一旦创建,它的标识就不会改变,可以把标识理解为对象在内存中的地址
is运算符比较的是对象的标识
id()函数返回对象标识的整数表示
"""
# 例 8-3 charles 和 lewis指代同一个对象
"""charles = {'name' : 'charles L.Dodgson','born':1832}
lewis = charles # lewis就是一个别名
print(charles is lewis) # True
print(id(lewis), id(charles)) # 2100165622272 2100165622272
lewis['balance'] = 950
print(charles) # {'name': 'charles L.Dodgson', 'born': 1832, 'balance': 950}
# 例 8-4 alex 与charles 比较的结果相等,但是alex不是charles
alex = {'name': 'charles L.Dodgson', 'born': 1832, 'balance': 950} # 内容一样
print(alex == charles) # True
print(alex is not charles) # True"""
# 8.2.1 == 和 is 的区别
"""
== 比较两个对象的值
is 比较两个对象的标识
最常使用is检查变量绑定的值是不是None
x is None
x is not None
"""
"""a = 1
b = a
print(a is b)
b = 1
print(a is b) # 注意此处a,b是同一个对象 是因为python对-5 ~ 255?的整数所做的优化 不新建对象
print(a == b)
c = 2000
d = 2000
print(c is d) #pycharm True 但Terminal python3.9 结果是False 还有一些简单字符串也有这个特性
"""
# 8.2.2元组的相对不可变性
"""
元组和多数python集合(列表.字典.集合等等)一样
保存的是对象的引用
str/bytes/array.array等扁平序列
保存的是数据本身
如果元组引用的元素是可变的,即便元组本身不可变,元素依然可变
"""
# 例 8-5 元组的值会随着引用的可变对象的变化而改变,元组中不可变的是对象的标识
"""t1 = (1,2,[30,40])
t2 = (1,2,[30,40])
print(t1 is t2) # False
print(t1 == t2) # True
print(id(t1[-1])) # 2464676277312
t1[-1].append(99)
print(t1)
print(id(t1[-1])) # 2761212980288
print(t1 == t2) # False"""
# 8.3 默认做浅复制
# 使用内置的类型构造方法浅复制
# l1 = [3,[55,44],(7,8,9)]
# l2 = list(l1)
# print(l2)
# print(l2 == l1)
# print(l2 is l1)
"""
>>>[3, [55, 44], (7, 8, 9)]
>>>True
>>>False"""
# 还可以使用[:]来做浅复制
# 例 8-6 为一个包含另一个列表的列表做浅复制
"""l1 = [3,[55,44],(7,8,9)]
l2 = list(l1)
l1.append(100)
l1[1].remove(55)
print('l1:',l1)
print('l2:',l2)
l2[1] += [33,22]
l2[2] += (10,11)
print('l1:',l1)
print('l2:',l2)"""
# 例8-8校车乘客在途中上车下车
"""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)
if __name__ == '__main__':
import copy
bus1 = Bus(['Alice','Bill','Claire','David'])
bus2 = copy.copy(bus1)
bus3 = copy.deepcopy(bus1)
print(id(bus1), id(bus2), id(bus3),sep='\n')
bus1.drop('Bill')
print(bus2.passengers)
print(id(bus1.passengers))
print(id(bus2.passengers))
print(id(bus3.passengers))
print(bus3.passengers)"""
"""
1583367671760
1583367671568
1583367670608
['Alice', 'Claire', 'David']
1583367678080
1583367678080
1583367657856
['Alice', 'Bill', 'Claire', 'David']
bus2 和 bus1 的passengers是同一个对象
但是bus3是作的深复制,所以不受影响"""
# 例8-10 循环引用
"""a = [10,20]
b = [a,30]
a.append(b)
print(a)
# >>>[10, 20, [[...], 30]]
from copy import deepcopy
c = deepcopy(a)
print(c)
"""
# 8.4函数的参数作为引用时
"""
Python的参数传递方式:共享传参(call by sharing)
共享传参:函数的各个形式参数获得实参中各个引用的副本
也就是说,函数内部的形参是实参的别名
结果是函数可能会修改作为参数传入的可变对象
"""
# 例 8-11 函数可能会修改接收到的任何可变对象
"""def f(a,b):
a += b
return a
x = 1
y = 2
print(f(x,y)) # 3
print(x,y) # 1 2
a = [1,2]
b = [3,4]
print(f(a,b)) # [1, 2, 3, 4]
print(a,b) # [1, 2, 3, 4] [3, 4] a被修改了
t = (10,20)
u = (30,40)
print(f(t,u)) # (10, 20, 30, 40)
print(t,u) # (10, 20) (30, 40)
"""
# 8.4.1不要使用可变类型作为参数的默认值
"""class HaunteBus:
'''备受幽林折磨的校车'''
def __init__(self,passengers = []): # 如果没有传入passengers参数,使用默认绑定的列表对象
self.passengers = passengers # self.passengers是passengers的一个别名
def pick(self,name):
self.passengers.append(name)
def drop(self,name):
self.passengers.remove(name)
bus1 = HaunteBus(['Alice','Bill'])
print(bus1.passengers)
bus1.pick('Charles')
bus1.drop('Alice')
print(bus1.passengers)
bus2 = HaunteBus()
bus2.pick('Carrie')
print(bus2.passengers)
bus3 = HaunteBus()
print(bus3.passengers) # ['Carrie']
bus3.pick('Dave')
print(bus2.passengers) # ['Carrie', 'Dave']
print(bus2.passengers is bus3.passengers) # True
print(bus1.passengers)
print(dir(HaunteBus.__init__))
print(dir(HaunteBus.__init__.__defaults__))
"""
# 8.4.2 防御可变参数
# 例 8-14,8-15
"""class TwilightBus:
def __init__(self, passengers=None):
if passengers is None:
self.passengers = []
else:
self.passengers = passengers # 此处把self.passengers变成passengers的一个别名
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
# 从TwilightBus下车后乘客消失了
basketball_team = ['Sue','Tina','Maya','Diana','Pat']
bus = TwilightBus(basketball_team)
bus.drop('Sue')
bus.drop('Pat')
print(basketball_team) # ['Tina', 'Maya', 'Diana']basketball_team被修改了
# TwilightBus违反了设计接口的最佳实践,"最小惊讶原则"
# 解决的方法是 校车维护自己的乘客列表 self.passengers = list(passengers)
"""
# 8.5 del和垃圾回收
"""
del语句删除名称,而不是对象
当删除的变量保存的是对象的最后一个引用或者无法得到对象时,对象会被当做垃圾回收
重新绑定也可能导致对象的引用数量归零导致对象被销毁
cpython中垃圾回收算法:引用计数"""
# 例 8-16没有指向对象的引用时,监视对象生命结束的情形
"""import weakref
s1= {1,2,3}
s2 = s1
def bye(): # 这个函数一定不能是被销毁对象的绑定方法,否则会有一个指向对象的引用
print('Gone with the wind...')
ender = weakref.finalize(s1,bye) # 调用finalize前,.alive属性为True
print(ender.alive)
del s1 # 删除对对象的引用
print(ender.alive)
s2 = 'spam' # 重新绑定s2,让{1,2,3}无法获取,对象被销毁,调用bye回调.ender.alive结果为False
print(ender.alive)
"""
# 8.6 弱引用
"""
正是因为有引用,对象才会在内存中存在
但是有时需要引用对象,而不让对象存在的时间超过所需时间,这经常用在缓存中
弱引用不会增加对象的引用数量
引用的目标对象称为所指对象
弱引用不会妨碍所指对象被当做垃圾回收
"""
# 例8-17 弱引用是可调用的对象,返回被引用的对象,如果所指对象不存在了,返回None
"""import weakref
a_set = {0,1}
wref = weakref.ref(a_set) # 创建弱引用对象
print(wref) # <weakref at 0x0000019575AD9E00; to 'set' at 0x0000019575B362E0>
print(wref()) # {0, 1}
a_set = {2,3,4}
print(wref()) # None"""
# 8.6.1 WeakValueDictionary简介
"""WeakValueDictionary类实现的是一种可变的映射,里面的值是对象的弱引用
被引用的对象在其他地方被当做垃圾回收以后,对应的键会自动从WeakValueDictionary中删除
WeakValueDictionary经常用于缓存"""
# weakreference.py
# 8.6.2弱引用的局限
"""
1.不是每个python对象都可以作为弱引用的目标
基本的list和dict实例不能作为所指对象
但是他们的子类可以轻松地解决这个问题
2.set实例可以作为所指对象
3.int和tuple实例不能作为所指对象,甚至他们的子类也不行"""
import weakref
class Mylist(list):
'''list的子类,实例可以作为弱引用的对象'''
a_list = Mylist(range(10))
# a_list可以作为弱引用的目标
wref_to_a_list = weakref.ref(a_list)
# 8.7 Python对不可变类型施加的把戏
# 对于元组t来说,t[:]和tuple(t)返回的都是同一个元组的引用
# 例 8-20 使用另一个元组构建元组,得到的其实是同一个元组
"""t1 = (1,2,3)
t2 = tuple(t1)
print(t2 is t1) # True
t3 = t1[:]
print(t3 is t1) # True"""
# str/byets/frozenset实例也有这种行为
# 例 8-21 字符串字面量可能会创建共享对象
t1 = (1,2,3)
t3 = (1,2,3)
print(t1 is t3) # pycharm True
s1 = 'abc'
s2 = 'abc'
print(s1 is s2) # True
# 这是cpython的优化措施,称为'驻留',这种情况也适用于一些整数,有的地方也称为'小数锯池'.主要是为了节省内存
# 本章小结
"""1.对象都有标识/类型/值,只有对象的值会不时变化
2.变量保存的是引用
3.简单的赋值,不创建副本
对于+=,*=等增量赋值来说,如果左边的变量绑定的是不可变对象,会创建新对象
否则,会就地修改
4.函数的参数是以别名的形式传递
使用可变类型作为函数参数的默认值有危险
5.del 只删除引用,而不真正销毁对象,对象只有引用为0时才会被垃圾回收
6.弱引用"""
weakreference.py
# 若引用模块weakref的应用
# 实现一个简单的类,表示各种奶酪
# 例8-18 cheese有个kind属性和标准的字符串表示形式
class Cheese:
def __init__(self,kind):
self.kind = kind
def __repr__(self):
return 'Cheese(%r)'%self.kind
"""
在catalog中的各种奶酪载入WeakValueDictionary实现的栈stock中
删除catalog后,stock中只剩下一种奶酪了"""
# 例 8-19 顾客:你们店里到底有没有奶酪
import weakref
stock = weakref.WeakValueDictionary()
catalog = [Cheese('Red Leicester'),Cheese('Tilsit'),
Cheese('Brie'),Cheese('Parmesan')]
for cheese in catalog:
# stock把奶酪的名称映射到catalog中Cheese实例的弱引用上
stock[cheese.kind] = cheese
print(sorted(stock.keys())) # stock的内容是完整的
# >>>['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']
del catalog # 删除catalog后
print(sorted(stock.keys())) # stock中的大部分奶酪不见了,为什么不是全部呢?
# for循环中的临时变量cheese变量引用了Parmesan所以Parmesan会被保留
# >>>['Parmesan']
del cheese # 显式删除cheese
print(sorted(stock.keys()))
# >>>[]
"""
与WeakValueDictionary对应的是WeakKeyDictionary
WeakKeyDictionary的键是弱引用
一些可能的应用:
可以为应用中其他部分拥有的对象附加数据
这样就无需为对象添加属性
这对覆盖属性访问权限的对象尤其有用
WeakSet类:
保存元素弱引用的集合类,元素没有强引用时,集合会把他们删除"""
|