larken

勤奋的人生才有价值

导航

第8章 对象引用、可变性和垃圾回收

  1 #《流畅的Python》读书笔记
  2 # 第四部分 面向对象惯用法
  3 # 第8章 对象引用、可变性和垃圾回收
  4 
  5 # 8.1 变量不是盒子
  6 # Python 变量类似于 Java 中的引用式变量,因此最好把它们理解为附加在对象上的标注。
  7 
  8 # 示例 8-1 变量 a 和 b 引用同一个列表,而不是那个列表的副本
  9 # 对引用式变量来说,说把变量分配给对象更合理,反过来说就有问题。
 10 # >>> a=[1,2,3]
 11 # >>> b=a
 12 # >>> b.append(4)
 13 # >>> a
 14 # [1, 2, 3, 4]
 15 
 16 # 示例 8-2 创建对象之后才会把变量分配给对象
 17 # 为了理解 Python 中的赋值语句,应该始终先读右边。
 18 #     对象在右边创建或获取,在此之后左边的变量才会绑定到对象上,这就像为对象贴上标注。
 19 # 因为变量只不过是标注,所以无法阻止为对象贴上多个标注。贴的多个标注,就是别名。
 20 # >>> x=Gizmo()
 21 # ❶ 输出的 Gizmo id: ... 是创建 Gizmo 实例的副作用。
 22 # Gizmo id: 34079216
 23 # ❷ 在乘法运算中使用 Gizmo 实例会抛出异常。
 24 # >>> y=Gizmo() * 10
 25 # ❸ 这里表明,在尝试求积之前其实会创建一个新的 Gizmo 实例。
 26 # Gizmo id: 35171312
 27 # ❹ 但是,肯定不会创建变量 y,因为在对赋值语句的右边进行求值时抛出了异常。
 28 # Traceback (most recent call last):
 29 #   File "<pyshell#9>", line 1, in <module>
 30 #     y=Gizmo() * 10
 31 # TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int'
 32 # >>> dir()
 33 # ['Gizmo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b', 'x']
 34 
 35 # 8.2 标识、相等性和别名
 36 # Lewis Carroll 是 Charles Lutwidge Dodgson 教授的笔名。Carroll 先生指的就是 Dodgson 教授,二者是同一个人。
 37 
 38 # 示例 8-3 charles 和 lewis 指代同一个对象
 39 # >>> charles={'name':'Charles L. Dodgson','born':1832}
 40 # >>> lewis=charles
 41 # >>> lewis is charles
 42 # True
 43 # >>> id(charles),id(lewis)
 44 # (34927120, 34927120)
 45 # >>> lewis['balance']=950
 46 # >>> charles
 47 # {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
 48 
 49 # 示例 8-4 alex 与 charles 比较的结果是相等,但 alex 不是charles
 50 # 在那段代码中,lewis 和 charles 是别名,即两个变量绑定同一个对象。
 51 #     而 alex 不是 charles 的别名,因为二者绑定的是不同的对象。
 52 #     alex 和 charles 绑定的对象具有相同的值(== 比较的就是值),但是它们的标识不同。
 53 # 每个变量都有标识、类型和值。
 54 #     对象一旦创建,它的标识绝不会变;你可以把标识理解为对象在内存中的地址。
 55 #     is 运算符比较两个对象的标识;id() 函数返回对象标识的整数表示。
 56 # >>> alex={'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
 57 # ❷ 比较两个对象,结果相等,这是因为 dict 类的 __eq__ 方法就是这样实现的。
 58 # >>> alex  == charles
 59 # True
 60 # ❸ 但它们是不同的对象。这是 Python 说明标识不同的方式:a is notb。
 61 # >>> alex is not charles
 62 # True
 63 
 64 # 8.2.1 在==和is之间选择
 65 # == 运算符比较两个对象的值(对象中保存的数据),而 is 比较对象的标识。
 66 # 通常,我们关注的是值,而不是标识,因此 Python 代码中 == 出现的频率比 is 高。
 67 
 68 # 8.2.2 元组的相对不可变性
 69 # 元组与多数 Python 集合(列表、字典、集,等等)一样,保存的是对象的引用。
 70 # 如果引用的元素是可变的,即便元组本身不可变,元素依然、可变。
 71 
 72 # 示例 8-5 一开始,t1 和 t2 相等,但是修改 t1 中的一个可变元素后,二者不相等了
 73 # ❶ t1 不可变,但是 t1[-1] 可变。
 74 # >>> t1=(1,2,[30,40])
 75 # ❷ 构建元组 t2,它的元素与 t1 一样。
 76 # >>> t2=(1,2,[30,40])
 77 # ❸ 虽然 t1 和 t2 是不同的对象,但是二者相等——与预期相符。
 78 # >>> t1 == t2
 79 # True
 80 # ❹ 查看 t1[-1] 列表的标识。
 81 # >>> id(t1[-1])
 82 # 33289600
 83 # ❺ 就地修改 t1[-1] 列表。
 84 # >>> t1[-1].append(99)
 85 # >>> t1
 86 # (1, 2, [30, 40, 99])
 87 # ❻ t1[-1] 的标识没变,只是值变了。
 88 # >>> id(t1[-1])
 89 # 33289600
 90 # ❼ 现在,t1 和 t2 不相等。
 91 # >>> t1 == t2
 92 # False
 93 
 94 # 8.3 默认做浅复制
 95 # 复制列表(或多数内置的可变集合)最简单的方式是使用内置的类型构造方法。
 96 # >>> l1=[3,[55,44],(7,8,9)]
 97 # ❶ list(l1) 创建 l1 的副本。
 98 # >>> l2=list(l1)
 99 # >>> l2
100 # [3, [55, 44], (7, 8, 9)]
101 # ❷ 副本与源列表相等。
102 # >>> l2 == l1
103 # True
104 # ❸ 但是二者指代不同的对象。对列表和其他可变序列来说,还能使用简洁的 l2 = l1[:] 语句创建副本。
105 # >>> l1 is l2
106 # False
107 
108 # 示例 8-6 为一个包含另一个列表的列表做浅复制;把这段代码复制粘贴到 Python Tutor 网站中,看看动画效果
109 # >>> l1=[3,[66,55,44],(7,8,9)]
110 # ❶ l2 是 l1 的浅复制副本。
111 # >>> l2=list(l1)
112 # ❷ 把 100 追加到 l1 中,对 l2 没有影响。
113 # >>> l1.append(100)
114 # ❸ 把内部列表 l1[1] 中的 55 删除。这对 l2 有影响,因为 l2[1] 绑定的列表与 l1[1] 是同一个。
115 # >>> l1[1].remove(55)
116 # >>> print('li',l1)
117 # li [3, [66, 44], (7, 8, 9), 100]
118 # >>> print('l2',l2)
119 # l2 [3, [66, 44], (7, 8, 9)]
120 # >>> l2[1] += [33,22]
121 # >>> l2[2] += (10,11)
122 # >>> print('l1',l1)
123 # l1 [3, [66, 44, 33, 22], (7, 8, 9), 100]
124 # >>> print('l2',l2)
125 # l2 [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]
126 
127 # 示例 8-8 校车乘客在途中上车和下车
128 class Bus:
129     def __init__(self,passengers):
130         if passengers is None:
131             self.passengers=[]
132         else:
133             self.passengers=list(passengers)
134     def pick(self,name):
135         self.passengers.append(name)
136     def drop(self,name):
137         self.passengers.remove(name)
138 
139 # 示例 8-9 使用 copy 和 deepcopy 产生的影响
140 # >>> import copy
141 # >>> bus1=Bus(['Alice','Bill','Claire','David'])
142 # >>> bus2=copy.copy(bus1)
143 # >>> bus3=copy.deepcopy(bus1)
144 # >>> id(bus1),id(bsu2),id(bus3)
145 # Traceback (most recent call last):
146 #   File "<pyshell#44>", line 1, in <module>
147 #     id(bus1),id(bsu2),id(bus3)
148 # NameError: name 'bsu2' is not defined
149 # >>> id(bus1),id(bus2),id(bus3)
150 # (35175888, 35175856, 37550768)
151 # ❷ bus1中的'Bill'下车后,bus2中也没有他了。
152 # >>> bus2.passengers
153 # ['Alice', 'Claire', 'David']
154 # >>> id(bus1.passengers),id(bus2.passengers),id(bus3.passengers)
155 # ❸ 审查passengers属性后发现,bus1和bus2共享同一个列表对象,因为bus2是bus1的浅复制副本。
156 # (37562848, 37562848, 37609760)
157 # ❹ bus3是bus1的深复制副本,因此它的passengers属性指代另一个列表。
158 # >>> bus3.passengers
159 # ['Alice', 'Bill', 'Claire', 'David']
160 
161 # 示例 8-10 循环引用:b 引用 a,然后追加到 a 中;deepcopy 会想办法复制 a
162 # >>> a=[10,20]
163 # >>> b=[a,30]
164 # >>> a.append(b)
165 # >>> a
166 # [10, 20, [[...], 30]]
167 # >>> from copy import deepcopy
168 # >>> c=deepcopy(a)
169 # >>> c
170 # [10, 20, [[...], 30]]
171 
172 # 8.4 函数的参数作为引用时
173 # Python 唯一支持的参数传递模式是共享传参(call by sharing)。
174 # 共享传参指函数的各个形式参数获得实参中各个引用的副本。
175 #     也就是说,函数内部的形参是实参的别名。
176 # >>> def f(a,b):
177 #     a += b
178 #     return a
179 # >>> x=1
180 # >>> y=2
181 # >>> f(x,y)
182 # 3
183 # ❶ 数字x没变。
184 # >>> x,y
185 # (1, 2)
186 # >>> a=[1,2]
187 # >>> b=[3,4]
188 # >>> f(a,b)
189 # [1, 2, 3, 4]
190 # ❷ 列表a变了。
191 # >>> a,b
192 # ([1, 2, 3, 4], [3, 4])
193 # >>> t=(10,20)
194 # >>> u=(30,40)
195 # >>> f(t,u)
196 # (10, 20, 30, 40)
197 # >>> t,u
198 # ❸ 元组t没变。
199 # ((10, 20), (30, 40))
200 
201 # 8.4.1 不要使用可变类型作为参数的默认值
202 # 可选参数可以有默认值,这是 Python 函数定义的一个很棒的特性,这样我们的 API 在进化的同时能保证向后兼容。
203 #     然而,我们应该避免使用可变的对象作为参数的默认值。
204 
205 # 示例 8-12 一个简单的类,说明可变默认值的危险
206 class HauntedBus:
207     """备受幽灵乘客折磨的校车"""
208     # ❶ 如果没传入passengers参数,使用默认绑定的列表对象,一开始是空列表。
209     def __init__(self,passengers=[]):
210         self.passengers = passengers
211     def pick(self,name):
212         self.passengers.append(name)
213     def drop(self,name):
214         self.passengers.remove(name)
215 
216 # 示例 8-13 备受幽灵乘客折磨的校车
217 # >>> bus1=HauntedBus(['Alice','Bill'])
218 # >>> bus1.passengers
219 # ['Alice', 'Bill']
220 # >>> bus1.pick('Charles')
221 # >>> bus1.drop('Alice')
222 # ❶ 目前没什么问题,bus1 没有出现异常。
223 # >>> bus1.passengers
224 # ['Bill', 'Charles']
225 # ❷ 一开始,bus2 是空的,因此把默认的空列表赋值给self.passengers。
226 # >>> bus2=HauntedBus()
227 # >>> bus2.pick('Carrie')
228 # >>> bus2.passengers
229 # ['Carrie']
230 # ❸ bus3 一开始也是空的,因此还是赋值默认的列表。
231 # >>> bus3=HauntedBus()
232 # ❹ 但是默认列表不为空!
233 # >>> bus3.passengers
234 # ['Carrie']
235 # ❺ 登上 bus3 的 Dave 出现在 bus2 中。
236 # >>> bus3.pick('Dave')
237 # >>> bus2.passengers
238 # ['Carrie', 'Dave']
239 # ❻ 问题是,bus2.passengers 和 bus3.passengers 指代同一个列表。
240 # >>> bus2.passengers is bus3.passengers
241 # True
242 # ❼ 但 bus1.passengers 是不同的列表。
243 # >>> bus1.passengers
244 # ['Bill', 'Charles']
245 
246 # 可以审查 HauntedBus.__init__ 对象,看看它的 __defaults__ 属性中的那些幽灵学生:
247 # >>> dir(HauntedBus.__init__)
248 # ['__annotations__', '__call__', ...,'__defaults__',...]
249 # >>> HauntedBus.__init__.__defaults__
250 # (['Carrie', 'Dave'],)
251 # 最后,我们可以验证 bus2.passengers 是一个别名,它绑定到HauntedBus.__init__.__defaults__ 属性的第一个元素上:
252 # >>> HauntedBus.__init__.__defaults__[0] is bus2.passengers
253 # True
254 
255 # 8.4.2 防御可变参数
256 # 如果定义的函数接收可变参数,应该谨慎考虑调用方是否期望修改传入的参数。
257 
258 # 示例 8-15 一个简单的类,说明接受可变参数的风险
259 class TwilightBus:
260     """让乘客销声匿迹的校车"""
261     def __init__(self,passengers=None):
262         if passengers is None:
263             # ❶ 这里谨慎处理,当passengers为None时,创建一个新的空列表。
264             self.passengers = []
265         else:
266             self.passengers = passengers
267     def pick(self,name):
268         self.passengers.append(name)
269     def drop(self,name):
270         self.passengers.remove(name)
271 
272 # 示例8 - 14 从TwilightBus下车后,乘客消失了
273 # ❶ basketball_team 中有 5 个学生的名字。
274 # >>> basketball_team=['Sue','Tina','Maya','Diana','pat']
275 # ❷ 使用这队学生实例化 TwilightBus。
276 # >>> bus=TwilightBus(basketball_team)
277 # ❸ 一个学生从 bus 下车了,接着又有一个学生下车了。
278 # >>> bus.drop('Tina')
279 # >>> bus.drop('pat')
280 # ❹ 下车的学生从篮球队中消失了!
281 # >>> basketball_team
282 # ['Sue', 'Maya', 'Diana']
283 
284 # 8.5 del和垃圾回收
285 # 对象绝不会自行销毁;然而,无法得到对象时,可能会被当作垃圾回收。
286 # del 语句删除名称,而不是对象。
287 #     del 命令可能会导致对象被当作垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时。
288 #     重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。
289 
290 # 示例 8-16 没有指向对象的引用时,监视对象生命结束时的情形
291 # >>> import weakref
292 # >>> s1={1,2,3}
293 # ❶ s1 和 s2 是别名,指向同一个集合,{1, 2, 3}。
294 # >>> s2=s1
295 # ❷ 这个函数一定不能是要销毁的对象的绑定方法,否则会有一个指向对象的引用。
296 # >>> def bye():
297 #     print('Gone    with the wind...')
298 # ❸ 在 s1 引用的对象上注册 bye 回调。
299 # >>> ender=weakref.finalize(s1,bye)
300 # ❹ 调用 finalize 对象之前,.alive 属性的值为 True。
301 # >>> ender.alive
302 # True
303 # >>> del s1
304 # ❺ 如前所述,del 不删除对象,而是删除对象的引用。
305 # >>> ender.alive
306 # True
307 # ❻ 重新绑定最后一个引用 s2,让 {1, 2, 3} 无法获取。对象被销毁了,调用了 bye 回调,ender.alive 的值变成了 False。
308 # >>> s2='spam'
309 # Gone    with the wind...
310 # >>> ender.alive
311 # False
312 
313 # 8.6 弱引用
314 # 正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。
315 #     但是,有时需要引用对象,而不让对象存在的时间超过所需时间。这经常用在缓存中。
316 
317 # 示例 8-17 弱引用是可调用的对象,返回的是被引用的对象;如果所指对象不存在了,返回 None
318 # >>> import weakref
319 # >>> a_set={0,1}
320 # ❶ 创建弱引用对象 wref,下一行审查它。
321 # >>> wref=weakref.ref(a_set)
322 # >>> wref
323 # <weakref at 0x023D19C0; to 'set' at 0x02185558>
324 # ❷ 调用wref()返回的是被引用的对象,{0, 1}。因为这是控制台会话,所以{0, 1}会绑定给_变量。
325 # >>> wref()
326 # {0, 1}
327 # ❸ a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _变量仍然指代它。
328 # >>> a_set={2,3,4}
329 # ❹ 调用 wref() 依旧返回 {0, 1}。
330 # >>> wref()
331 # {0, 1}
332 # ❺ 计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。
333 #     但是,随后 _ 绑定到结果值 False。现在 {0, 1} 没有强引用了。
334 # >>> wref() is None
335 # ❻ 因为 {0, 1} 对象不存在了,所以 wref() 返回 None。
336 # False
337 # >>> wref() is None
338 # True
339 
340 # 8.6.1 WeakValueDictionary简介
341 # WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。
342 #     被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从 WeakValueDictionary 中删除。
343 #     因此,WeakValueDictionary 经常用于缓存。
344 class Cheese:
345     def __init__(self,kind):
346         self.kind=kind
347     def __repr__(self):
348         return 'Cheese(%r)'%self.kind
349 
350 # 示例 8-19 顾客:“你们店里到底有没有奶酪?”
351 >>> import weakref
352 >>> stock = weakref.WeakValueDictionary()
353 >>> catalog=[Cheese('red Leicester'),Cheese('Tilsit'),Cheese('Brie'),Cheese('Parmesan')]
354 >>> for cheese in catalog:
355     stock[cheese.kind]=cheese
356 >>> sorted(stock.keys())
357 ['Brie', 'Parmesan', 'Tilsit', 'red Leicester']
358 >>> del catalog
359 >>> sorted(stock.keys())
360 ['Parmesan']
361 >>> del cheese
362 >>> sorted(stock.keys())
363 []
364 
365 # 8.6.2 弱引用的局限
366 # 不是每个 Python 对象都可以作为弱引用的目标(或称所指对象)。基本的 list 和 dict 实例不能作为所指对象,但是它们的子类可以轻松地解决这个问题:
367 
368 # 8.7 Python对不可变类型施加的把戏
369 # 你可以放心跳过本节。这里讨论的是 Python 的实现细节,对Python 用户来说没那么重要。
370 
371 # 8.8 本章小结
372 # 每个 Python 对象都有标识、类型和值。只有对象的值会不时变化。
373 
374 # 8.9 延伸阅读

 

posted on 2019-03-22 00:57  larken  阅读(247)  评论(0编辑  收藏  举报