- ==运算符比较两个对象的值(对象中保存的数据),而is比较对象的标识。
- 通常,我们关注的是值,而不是标识,因此Python代码中==出现的频率比is高。
- 然而,在变量和单例值之间比较时,应该使用is。目前,最常使用is检查变量绑定的值是不是None。
- is运算符比==速度快,因为它不能重载,所以Python不用寻找并调用特殊方法,而是直接比较两个整数ID。
- 而a==b是语法糖,等同于a.eq(b)。继承自object的__eq__方法比较两个对象的ID,结果与is一样。
- 元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。
>>> t1 = (1, 2, [30, 40])
>>> t2 = (1, 2, [30, 40])
>>> t1 == t2
True
>>> id(t1[-1])
140443908678088
>>> t1[-1].append(99)
>>> t1
(1, 2, [30, 40, 99])
>>> id(t1[-1])
140443908678088
>>> t1 == t2
False-
构造方法或[:]做的是浅复制(即复制了最外层容器,副本中的元素是源容器中元素的引用)
-
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)
(139824757088496, 139824750024912, 139824749909328)
>>> bus1.drop('Bill')
>>> bus2.passengers
['Alice', 'Claire', 'David']
>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)
(139824757117512, 139824757117512, 139824689681928)
>>> bus3.passengers
['Alice', 'Bill', 'Claire', 'David']- 注意,一般来说,深复制不是件简单的事。如果对象有循环引用,那么这个朴素的算法会进入无限循环。
>>> a = [10, 20]
>>> b = [a, 30]
>>> a.append(b)
>>> a
[10, 20, [[...], 30]]
>>> from copy import deepcopy
>>> c = deepcopy(a)
>>> c
[10, 20, [[...], 30]]- Python唯一支持的参数传递模式是共享传参(call by sharing)。多数面向对象语言都采用这一模式,包括Ruby、Smalltalk和Java(Java的引用类型是这样,基本类型按值传参)。
- 共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是说,函数内部的形参是实参的别名。
>>> 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))函数可能会修改接收到的任何可变对象.
可变默认值导致的这个问题说明了为什么通常使用None作为接收可变值的参数的默认值。
如果定义的函数接收可变参数,应该谨慎考虑调用方是否期望修改传入的参数。
弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。
弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。
>>> import weakref
>>> a_set = {0, 1}
>>> wref = weakref.ref(a_set) #1
>>> wref
<weakref at 0x7f2b7c8cb048; to 'set' at 0x7f2b78f90e48>
>>> wref() #2
{0, 1}
>>> a_set = {2,3,4} #3
>>> wref() #4
{0, 1}
>>> wref() is None #5
False
>>> wref() is None #6
True❶ 创建弱引用对象wref,下一行审查它。
❷ 调用wref( )返回的是被引用的对象,{0, 1}。因为这是控制台会话,所以{0, 1}会绑定给_变量。_
_❸ a_set不再指代{0, 1}集合,因此集合的引用数量减少了。但是_变量仍然指代它。
❹ 调用wref( )依旧返回{0, 1}。
❺ 计算这个表达式时,{0, 1}存在,因此wref( )不是None。但是,随后_绑定到结果值False。现在{0, 1}没有强引用了。
❻ 因为{0, 1}对象不存在了,所以wref( )返回None。
weakref模块的文档指出,weakref.ref类其实是低层接口,供高级用途使用,多数程序最好使用weakref集合和finalize。也就是说,应该使用WeakKeyDictionary、WeakValueDictionary、WeakSet和finalize(在内部使用弱引用),不要自己动手创建并处理weakref.ref实例。
WeakValueDictionary类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从WeakValueDictionary中删除。因此,WeakValueDictionary经常用于缓存。
>>> class Cheese:
... def __init__(self, kind):
... self.kind = kind
... def __repr__(self):
... return 'Cheese(%r)' % self.kind
...
>>> import weakref
>>> stock = weakref.WeakValueDictionary() #1
>>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'), Cheese('Brie'), Cheese('Parmesan')]
>>> for cheese in catalog:
... stock[cheese.kind] = cheese #2
...
>>> sorted(stock.keys())
['Brie', 'Parmesan', 'Red Leicester', 'Tilsit'] #3
>>> del catalog
>>> sorted(stock.keys())
['Parmesan'] #4
>>> del cheese
>>> sorted(stock.keys())
[]❶ stock是WeakValueDictionary实例。
❷ stock把奶酪的名称映射到catalog中Cheese实例的弱引用上。
❸ stock是完整的。
❹ 删除catalog之后,stock中的大多数奶酪都不见了,这是WeakValueDictionary的预期行为。为什么不是全部呢?
临时变量引用了对象,这可能会导致该变量的存在时间比预期长。通常,这对局部变量来说不是问题,因为它们在函数返回时会被销毁。但是在示例8-19中,for循环中的变量cheese是全局变量,除非显式删除,否则不会消失。
与WeakValueDictionary对应的是WeakKeyDictionary,后者的键是弱引用。
weakref模块还提供了WeakSet类,按照文档的说明,这个类的作用很简单:“保存元素弱引用的集合类。元素没有强引用时,集合会把它删除。”如果一个类需要知道所有实例,一种好的方案是创建一个WeakSet类型的类属性,保存实例的引用。如果使用常规的set,实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与Python进程一样长,除非显式删除类。
不是每个Python对象都可以作为弱引用的目标(或称所指对象)。基本的list和dict实例不能作为所指对象,但是它们的子类可以轻松地解决这个问题
如果两个变量指代的不可变对象具有相同的值(a==b为True),实际上它们指代的是副本还是同一个对象的别名基本没什么关系,因为不可变对象的值不会变,但有一个例外。这里说的例外是不可变的集合,如元组和frozenset:如果不可变集合保存的是可变元素的引用,那么可变元素的值发生变化后,不可变集合也会随之改变。实际上,这种情况不是很常见。不可变集合不变的是所含对象的标识。
每门面向对象的语言至少都有一种获取对象的字符串表示形式的标准方式。Python提供了两种方式。
repr() 以便于开发者理解的方式返回对象的字符串表示形式。
str() 以便于用户理解的方式返回对象的字符串表示形式。
我们要实现__repr__和__str__特殊方法,为repr( )和str( )提供支持。
__bytes__和__format__。__bytes__方法与__str__方法类似:bytes( )函数调用它获取对象的字节序列表示形式。而__format__方法会被内置的format( )函数和str.format( )方法调用,使用特殊的格式代码显示对象的字符串表示形式。
在Python 3中,__repr__、__str__和__format__都必须返回Unicode字符串(str类型)。只有__bytes__方法应该返回字节序列(bytes类型)。