Python 对象引用

元组的相对不可变性

>>> t1 = (1, 2, [30, 40])
>>> t2 = (1, 2, [30, 40])
>>> t1 == t2
True
>>> id(t1[-1])
4302515784
>>> t1[-1].append(99)
>>> t1
(1, 2, [30, 40, 99])
>>> id(t1[-1])
4302515784
>>> t1 == t2
False

默认浅复制

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

使用 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)

>>> from Bus import Bus
>>> import copy
>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
>>> bus2 = copy.copy(bus1)
>>> bus3 = copy.deepcopy(bus1)
>>> id(bus1), id(bus2), id(bus3)
(49126312, 48367432, 49224616)
>>> bus1.drop('Bill')
>>> bus2.passengers
['Alice', 'Claire', 'David']
>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)
(49175944, 49175944, 49044552)
>>> bus3.passengers
['Alice', 'Bill', 'Claire', 'David']

可以实现特殊方法 __copy__() 和 __deepcopy__(), 控制 copy 和 deepcopy 的行为。

函数的参数为引用

>>> 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))

不要使用可变类型作为参数的默认值

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)


>>> from HauntedBus import HauntedBus
>>> bus1 = HauntedBus(['Alice', 'Bill'])
>>> bus1.passengers
['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']

del 与垃圾回收  

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

销毁实例时,Python 解释器会调用 __del__ 方法。

使用 weakref.finalize 注册一个回调函数, 在销毁对象时调用。  

>>> import weakref
>>> s1 = {1, 2, 3}
>>> s2 = s1
>>> def bye():
	print('bye...')

	
>>> ender = weakref.finalize(s1, bye)
>>> ender.alive
True
>>> del s1
>>> ender.alive
True
>>> s2 = 'abc'
bye...
>>> ender.alive
False

弱引用

弱引用不会增加对象的引用数量。 引用的目标对象称为所指对象 (referent) 。 因此, 弱引用不会妨碍所指对象被当作垃圾回收。

>>> import weakref
>>> a_set = {0, 1}
>>> wref = weakref.ref(a_set)
>>> wref
weakref at 0x0000000002EE6598; to 'set' at 0x0000000002EC2AC8
>>> wref()
{0, 1}
>>> a_set = {2, 3, 4}
>>> wref()
{0, 1}
>>> wref() is None
False
>>> wref() is None
True

1. 调用 wref() 返回的是被引用的对象, {0, 1}。 因为这是控制台会话, 所以 {0, 1} 会绑定给 _ 变量。

2. a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _ 变量仍然指代它。

3. 计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。但是,随后 _ 绑定到结果值 False。现在 {0, 1} 没有强引用了。


WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。

class Cheese:

    def __init__(self, kind):
        self.kind = kind

    def __repr__(self):
        return 'Cheese(%r)' % self.kind


>>> import weakref
>>> stock = weakref.WeakValueDictionary()
>>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'),
... Cheese('Brie'), Cheese('Parmesan')]
...
>>> for cheese in catalog:
... stock[cheese.kind] = cheese
...
3
3
4
4>>> sorted(stock.keys())
['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']
>>> del catalog
>>> sorted(stock.keys())
['Parmesan']
>>> del cheese
>>> sorted(stock.keys())
[]

弱引用的局限  

基本的 list 和 dict 实例不能作为弱引用的目标(或称所指对象), 但是它们的子类可以。

class MyList(list):
	"""list的子类, 实例可以作为弱引用的目标"""

a_list = MyList(range(10))
# a_list可以作为弱引用的目标
wref_to_a_list = weakref.ref(a_list)	


注意:

Python 控制台中输入 help(tuple), 你会看到这句话: 如果参数是一个元组, 那么返回值是同一个对象。

对元组 t 来说, t[:] 不创建副本, 而是返回同一个对 象的引用。 此外, tuple(t) 获得的也是同一个元组的引用。

>>> t1 = (1, 2, 3)
>>> t2 = tuple(t1)
>>> t2 is t1
True
>>> t3 = t1[:]
>>> t3 is t1
True

字符串字面量可能会创建共享的对象

>>> t1 = (1, 2, 3)
>>> t3 = (1, 2, 3)
>>> t3 is t1 
False
>>> s1 = 'ABC'
>>> s2 = 'ABC' 
>>> s2 is s1 
True

共享字符串字面量是一种优化措施, 称为驻留(interning

展开阅读全文