本章先以一个比喻说明Python的变量:变量是标注,而不是盒子(variables are labels,not boxes)。
8.1 变量不是盒子
>>> a = [1, 2, 3]
>>> b = a
>>> a.append(4)
>>> b
[1, 2, 3, 4]
变量只不过是标注,所有无法阻止为对象贴上多个标注。贴的多个标注,就是别名。
8.2 标识、相等性和别名
示例8-3 charles和lewis指代同一个对象
>>> charles = {'name':'Charles L. Dodgson', 'born':1832}
>>> lewis = charles
>>> lewis is charles
True
>>> id(charles), id(lewis)
(1367254942848, 1367254942848)
>>> lewis['balance'] = 950
>>> charles
{'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
示例8-4 测试另一对象alex
{'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
>>> alex = {'name':'Charles L. Dodgson', 'born':1832, 'balance':950}
>>> alex == charles
True
>>> alex is charles
False
每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会;可以把标识理解为对象在内存中的地址。is运算符比较两个对象的标识;id()函数返回对象标识的整数表示。
编程中很少使用id()函数。标识最常使用is运算符检查,而不是直接比较ID。
8.2.1 在==和is之间选择
==运算符比较两个对象的值,而is比较两个对象的标识。
目前,最常使用is检查变量绑定的值是不是None。下面是推荐的写法:
x is None
否定的正确写法是:
x is not None
is运算符比==速度快,因为它不能重载,所以Python不用寻找并调用特殊方法,而是直接比较两个整数的ID。而a==b是语法糖,等同于a.__eq__(b)。
8.2.2 元组的相对不可变性
元组与多数Python集合一样,保存的是对象的引用。如果引用的元素是可变的,即便元组本身不可变,元素依然可变。
>>> t1 = (1, 2, [30, 40])
>>> t2 = (1, 2, [30, 40])
>>> t1 == t2
True
>>> id(t1[-1])
1367255527624
>>> t1[-1].append(99)
>>> t1
(1, 2, [30, 40, 99])
>>> id(t1[-1])
1367255527624
>>> t1 == t2
False
8.3 默认做浅拷贝
复制列表(或多数内置的可变集合)最简单的方式是使用内置的类型构造方法。
>>> L1 = [3, [55, 44], (7, 8, 9)]
>>> L2 = list(L1) #创建L1的副本
>>> L2
[3, [55, 44], (7, 8, 9)]
>>> L1 == L2
True
>>> L2 is L1
False
为任意对象做深拷贝和浅拷贝
有时我们需要的是深拷贝(即副本不共享内部对象的引用)。copy模块提供的deepcopy和copy函数能为任意对象做深拷贝和浅拷贝。
示例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)
示例8-9 使用copy和deepcopy
>>> import copy
>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
>>> bus2 = copy.copy(bus1)
>>> bus3 = copy.deepcopy(bus1)
>>> id(bus1), id(bus2), id(bus3)
(1367262177264, 1367262177152, 1367262177488)
>>> bus1.drop('Bill')
>>> bus2.passengers
['Alice', 'Claire', 'David']
>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)
(1367262164936, 1367262164936, 1367262069704)
>>> bus3.passengers
['Alice', 'Bill', 'Claire', 'David']
示例8-10 循环引用:b引用a,然后追加到a中;deepcopy会想办法复制啊
>>> a = [10, 20]
>>> b = [a, 30]
>>> a.append(b)
>>> a
[10, 20, [[...], 30]]
>>> from copy import deepcopy
>>> c = deepcopy(a)
>>> c
[10, 20, [[...], 30]]
8.4 函数的参数作为引用时
Python唯一支持的参数传递模式是共享传参(call by sharing)。
共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是说,函数内部的形参是实参的别名。
示例8-11 函数可能会修改接收到的任意可变对象
>>> def f(a, b):
... a += b
... return a
...
>>> x = 1
>>> y = 2
>>> f(x, y)
3
>>> x, y
(1, 2)
>>> a = [1, 2]
>>> b = [3, 4]
>>> f(a, b)
[1, 2, 3, 4]
>>> a, b
([1, 2, 3, 4], [3, 4])
>>> t = (10, 20)
>>> u = (30, 40)
>>> f(t, u)
(10, 20, 30, 40)
>>> t, u
((10, 20), (30, 40))
>>>
8.4.1 不要使用可变类型作为参数的默认值
可选参数可以有默认值,这是Python函数定义的一个很棒的特性,这样我们的API在进化的同时能保证向后兼容。然而,我们应该避免使用可变对象作为参数的默认值。
示例8-12 一个简单的类,说明可变默认值的危险
>>> 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)
示例8-13 备受幽灵乘客折磨的校车
>>> bus1 = HaunterdBus(['Alice', 'Bill'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'HaunterdBus' is not defined
>>> bus1 = HauntedBus(['Alice', 'Bill'])
>>> bus1.pick('Charlie')
>>> bus1.drop('Alice')
>>> bus1.passengers
['Bill', 'Charlie']
>>> bus2 = HauntedBus()
>>> bus2.pick('Carrie')
>>> bus2.passengers
['Carrie']
>>> bus3 = HauntedBus()
>>> bus3.passengers
['Carrie']
>>> bus3.pick('Dave')
>>> bus2.passengers
['Carrie', 'Dave']
>>> bus2.passengers is bus3.passengers
True
>>> bus1.passengers
['Bill', 'Charlie']
>>>
问题在于,没有指定初始乘客的HauntedBus实例会共享同一个乘客列表。
可以审查HauntedBus.__init__对象,看看它的__defaults__属性中的那些幽灵学生:
>>> dir(HauntedBus.__init__)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> HauntedBus.__init__.__defaults__
(['Carrie', 'Dave'],)
可变默认值导致的这个问题说明了为什么通常使用None作为接收可变值的参数的默认值。
8.4.2 防御可变参数
>>> class TwilightBus:
... def __init__(self, passengers=None):
... if passengers is None:
... self.passengers = []
... else:
... self.passengers = list(passengers) # 创建passengers类别的副本
8.5 del和垃圾回收
del语句删除名称,而不是对象。
示例8-16: 没有指向对象的引用时,监视对象生命结束时的情形
>>> import weakref
>>> s1 = {1,2,3}
>>> s2 = s1
>>> def bye():
... print('Gone with the wind...')
...
>>> ender.weakref.finalize(s1, bye)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'ender' is not defined
>>> ender = weakref.finalize(s1, bye)
>>> ender.alive
True
>>> del s1
>>> ender.alive
True
>>> s2 = 'spam'
Gone with the wind...
>>> ender.alive
False
上面的示例目的是明确指出del不会删除对象,但是执行del操作后可能会导致对象不可获取,从而被删除。
8.6 弱引用
正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象的存在的时间超过所需的时间。这经常用在缓存。
弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。弱引用不会妨碍所指对象被当做垃圾回收。
弱引用在缓存中很有用,因为我们不想紧因为被缓存引用着而始终保存缓存对象。
8.6.1 WeakValueDictionary简介
WeakValueDictionary类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当做垃圾回收后,对应的键会自动从WeakValueDictionary中删除。因此,WeakValueDictionary经常用于缓存。
示例8-18 Cheese有个kind属性和标准的字符串表示形式
>>> class Cheese:
... def __init__(self, kind):
... self.kind = kind
... def __repr__(self):
... return 'Cheese(%r)'%self.kind
...
示例8-19 顾客:“你们店里到底有没有奶酪?”
>>> import weakref
>>> stock = weakref.WeakValueDictionary()
>>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'), Cheese('Brie'), Cheese('parmesan')]
>>> for cheese in catalog:
... stock[cheese.kind] = cheese
...
>>> sorted(stock.keys())
['Brie', 'Red Leicester', 'Tilsit', 'parmesan']
>>> del catalog
>>> sorted(stock.keys())
['parmesan']
>>> del cheese
>>> sorted(stock.keys())
[]
示例8-19中,for循环中的变量cheese是全局变量,除非显式删除,否则不会消失。