1:

变量不是盒子,应该把变量视作便利贴。变量只不过是标注,所以无法阻止为对象贴上多个标注。标注就是别名:

>>> a = [1, 2, 3]>>> b =a>>> a.append(4)>>>b

[1, 2, 3, 4]

下面的代码中,lewis 和 charles 是别名,即两个变量绑定同一个对象。而 alex 不是 charles 的别名,因为二者绑定的是不同的对象。alex 和 charles 绑定的对象具有相同的值(== 比较的就是值),但是它们的标识不同。

>>> charles = {'name': 'Charles L. Dodgson', 'born': 1832}>>> lewis =charles>>> lewis ischarles
True>>>id(charles), id(lewis)
(140334566757552, 140334566757552)>>> lewis['balance'] = 950
>>>charles
{'born': 1832, 'balance': 950, 'name': 'Charles L. Dodgson'}>>> alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}>>> alex ==charles
True>>> alex is notcharles
True

2:

每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变;可以把标识理解为对象在内存中的地址。

is 运算符比较两个对象的标识;id() 函数返回对象标识的整数表示。== 运算符比较两个对象的值(对象中保存的数据)。is 运算符比 == 速度快,因为它不能重载,所以 Python 不用寻找并调用特殊方法,而是直接比较两个整数

3:

元组与多数 Python 集合(列表、字典、集合等)一样,保存的是对象的引用。如果引用的元素是可变的,即便元组本身不可变,元素依然可变。也就是说,元组的不可变性其实是指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。

而 str、bytes 和 array.array 等单一类型序列是扁平的,它们保存的不是引用,而是在连续的内存中保存数据本身(字符、字节和数字)。

4:

复制列表(或多数内置的可变集合)最简单的方式是使用内置的类型构造方法。例如:

>>> l1 = [3, [55, 44], (7, 8, 9)]>>> l2 =list(l1)>>> l3 =l1>>>l2
[3, [55, 44], (7, 8, 9)]>>>l3
[3, [55, 44], (7, 8, 9)]>>> l2 isl1
False>>> l3 isl1
True

对列表和其他可变序列来说,还能使用简洁的 l2 = l1[:]语句创建副本。

构造方法或 [:] 做的是浅复制(即复制了最外层容器,副本中的元素是源容器中元素的引用)。如果所有元素都是不可变的,那么这样没有问题,还能节省内存。但是,如果有可变的元素,可能就会导致意想不到的问题:

>>> l1 = [3, [66, 55, 44], (7, 8, 9)]>>> l2 =list(l1)>>> l1.append(100)>>> l1[1].remove(55)>>> print('l1:', l1)
('l1:', [3, [66, 44], (7, 8, 9), 100])>>> print('l2:', l2)
('l2:', [3, [66, 44], (7, 8, 9)])>>> l2[1] += [33, 22]>>> l2[2] += (10, 11)>>> print('l1:', l1)
('l1:', [3, [66, 44, 33, 22], (7, 8, 9), 100])>>> print('l2:', l2)
('l2:', [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)])

有时我们需要的是深复制(即副本不共享内部对象的引用)。copy 模块提供的 deepcopy 和 copy 函数能为任意对象做深复制和浅复制。

>>> classBus:
...def __init__(self, passengers=None):
...if passengers isNone:
... self.passengers=[]
...else:
... self.passengers=list(passengers)
...defpick(self, name):
... self.passengers.append(name)
...defdrop(self, name):
... self.passengers.remove(name)
...>>> importcopy>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])>>> bus2 =copy.copy(bus1)>>> bus3 =copy.deepcopy(bus1)>>> bus4 =bus1>>>
>>> printid(bus1), id(bus2), id(bus3), id(bus4)140334566771960 140334566290640 140334566290928 140334566771960
>>> bus1.drop('Bill')>>>bus2.passengers
['Alice', 'Claire', 'David']>>>bus3.passengers
['Alice', 'Bill', 'Claire', 'David']>>>bus4.passengers
['Alice', 'Claire', 'David']>>>
>>> printid(bus1.passengers), id(bus2.passengers), id(bus3.passengers), id(bus4.passengers)140334566290568 140334566290568 140334566768792 140334566290568
5:

Python 中,函数内部的形参是实参的别名。这种方案的结果是,函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(即不能把一个对象替换成另一个对象):

>>> deff(a, b):
... a+=b
...returna
...>>> 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))
对元组来说,+= 运算符创建一个新元组,
6:
应该避免使用可变的对象作为参数的默认值。比如下面的例子:
>>> classHauntedBus:
...def __init__(self, passengers=[]):
... self.passengers=passengers
...defpick(self, name):
... self.passengers.append(name)
...defdrop(self, name):
... self.passengers.remove(name)
...>>>
>>> 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 isbus3.passengers
True>>>
>>>bus1.passengers
['Bill', 'Charlie']>>> HauntedBus.__init__.__defaults__(['Carrie', 'Dave'],)

实例化 HauntedBus 时,如果传入乘客,会按预期运作。但是不为 HauntedBus 指定乘客的话,奇怪的事就发生了,没有指定初始乘客的 HauntedBus 实例会共享同一个乘客列表。self.passengers 变成了 passengers 参数默认值的别名。出现这个问题的根源是,默认值在定义函数时计算(通常在加载模块时),这样默认值变成了函数对象的属性。因此,如果默认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影响。

7:

del 语句删除名称,而不是对象。del 命令可能会导致对象被当作垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时。 重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。

如果两个对象相互引用,当它们的引用只存在二者之间时,垃圾回收程序会判定它们都无法获取,进而把它们都销毁。

在 CPython 中,垃圾回收使用的主要算法是引用计数。实际上,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用__del__ 方法(如果定义了),然后释放分配给对象的内存。

为了演示对象生命结束时的情形,下面使用 weakref.finalize (python3)注册一个回调函数,在销毁对象时调用。

>>> importweakref>>> s1 = {1, 2, 3}>>> s2 =s1>>> defbye():
...print('Gone with the wind...')
...>>> ender =weakref.finalize(s1, bye)>>>ender.alive
True>>>
>>> dels1>>>ender.alive
True>>>
>>> s2 = 'spam'Gone with the wind...>>>ender.alive
False

正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间。这经常用在缓存中。弱引用不会增加对象的引用数量。弱引用不会妨碍所指对象被当作垃圾回收。弱引用在缓存应用中很有用,

下例展示了如何使用 weakref.ref 实例获取所指对象。如果对象存在,调用弱引用可以获取对象;否则返回 None。

>>> importweakref>>> a_set = {0, 1}>>> wref =weakref.ref(a_set)>>>wref
>>>
>>>wref()
set([0,1])>>>
>>> a_set = {2, 3, 4}>>>wref()
set([0,1])>>>
>>> wref() isNone
False>>>
>>> wref() isNone
True

上例是一个控制台会话,而Python 控制台会自动把 _ 变量绑定到结果不为 None 的表达式结果上。首次调用 wref() 返回的是被引用的对象{0, 1}, {0, 1} 会绑定给 _ 变量。接下来,a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _ 变量仍然指代它。所以再次调用 wref() 依旧返回 {0, 1}。计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。但是,随后 _ 绑定到结果值 False。现在 {0, 1} 没有强引用了。因为 {0, 1} 对象不存在了,所以 wref() 返回 None

weakref 模块的文档(http://docs.python.org/3/library/weakref.html)指出,weakref.ref类其实是低层接口,供高级用途使用,多数程序最好使用 weakref 集合和 finalize。也就是说,应该使用 WeakKeyDictionary、WeakValueDictionary、WeakSet 和finalize(在内部使用弱引用),不要自己动手创建并处理 weakref.ref 实例。

WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从 WeakValueDictionary中删除。因此,WeakValueDictionary 经常用于缓存。如下例:

>>> classCheese:
...def __init__(self, kind):
... self.kind=kind
...def __repr__(self):
...return 'Cheese(%r)' %self.kind
...>>> importweakref>>> stock =weakref.WeakValueDictionary()>>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'), Cheese('Brie'), Cheese('Parmesan')]>>>
>>> for cheese incatalog:
... stock[cheese.kind]=cheese
...>>>sorted(stock.keys())
['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']>>>
>>> delcatalog>>>sorted(stock.keys())
['Parmesan']>>>
>>> delcheese>>>sorted(stock.keys())
[]

删除 catalog 之后,stock 中的大多数奶酪都不见了,这是 WeakValueDictionary的预期行为。为什么不是全部呢?这是因为for循环中的变量 cheese 是全局变量,除非显式删除,否则不会消失。

不是每个 Python 对象都可以作为弱引用的目标(或称所指对象)。基本的 list 和 dict实例不能作为所指对象,但是它们的子类可以轻松地解决这个问题:

>>> wref_to_a_list =weakref.ref([])
Traceback (most recent call last):
File"", line 1, in TypeError: cannot create weak reference to'list'objectclassMyList(list):passa_list= MyList(range(10))#a_list可以作为弱引用的目标
wref_to_a_list = weakref.ref(a_list)
8:

如果所有 Python 对象都是不可变的,那么本章就没有存在的必要了。处理不可变的对象时,变量保存的是真正的对象还是共享对象的引用无关紧要。仅当对象可变时,对象标识才重要。

CPython 中的垃圾回收主要依靠引用计数,这容易实现,但是遇到引用循环容易泄露内存,因此 CPython 2.0实现了分代垃圾回收程序,它能把引用循环中不可获取的对象销毁。