参考《流畅的python》第8章
变量是盒子
变量是附加在对象上的标注。
1. 每个变量都有标识、类型和值。
可以把标识理解为对象在内存中的地址。对象一旦创建,它的标识绝不会变。
2. is运算符 和 ==
is 运算符比较两个对象的标识;id() 函数返回对象标识的整数表示。对象 ID 的真正意义在不同的实现中有所不同。在 CPython 中,id() 返回对象的内存地址,但是在其他 Python 解释器中可能是别的值。关键是,ID 一定是唯一的数值标注,而且在对象的生命周期中绝不会变。
== 运算符比较两个对象的值(对象中保存的数据),而 is 比较对象的标识。
在变量和单例值之间比较时,应该使用 is。目前,最常使用 is
检查变量绑定的值是不是 None。下面是推荐的写法
x is None
x is not None
is 运算符比 == 速度快,因为 Python 不用寻找并调用特殊方法,而是直接比较两个整数 ID。
a == b
是语法糖,等同于a.__eq__(b)
。
继承自 object 的 __eq__
方法比较两个对象的 ID,结
果与 is 一样。但是多数内置类型使用更有意义的方式覆盖了 __eq__
方法,会考虑对象属性的值。
3. 元组的相对不可变性
元组与多数 Python 集合(列表、字典、集)一样,保存的是对象的引用。(str, bytes, array.array则是单一类型序列,保存的是连续内存中的数据本身。)
元组的不可变性其实是指 tuple 保存的引用不可变,即每个元素的id不可变,与所引用的对象是否变化无关。
可理解为,tuple是个保存常量指针的数组,每个元素都是个常量指针,其指向固定的内存地址。
元组是不可变的,但如果引用的元素是可变的,元素依然
可变。
t1 = (1, 2, [30, 40])
t2 = (1, 2, [30, 40])
t1 == t2 # True
# id(t1[-1]) == 4302515784
t1[-1].appned(40)
# t1 变为 (1, 2, [30, 40, 50])
# id(t1[-1]) == 4302515784 #且第3个元素的id没有变
t1 == t2 # False,t1 t2值不想等了。
4. 浅复制 VS 深复制
4.1 浅复制
即复制了最外层容器,副本中的元素是原容器中元素的引用。浅复制容易操作,但是得到的结果可能并不是你想要的。
在 http://www.pythontutor.com 中演示以下代码的执行过程:
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1) #浅复制,同 l2 = l1[:]
l1.append(100)
l1[1].remove(55)
l2[1] += [33, 22]
l2[2] += (10, 11)
l1中每个元素是一个对象的引用。
执行浅复制,l2的元素是l1元素的引用的复制,即指向相同的引用对象。通过l1修改所第2个元素列表内容,会使得l2的内容发生同样的变化。
倒数第2句,会直接修改所引用的列表的内容,l1和l2第2个元素还是都指向此列表。
最后一句,元组是不可变的,所以会创建一个新的元组对象,并幅值给l2的第3个元素,此后,l1和l2的第3个元素指向不同的对象。
4.2 深复制
副本与原对象不共享内部元素的引用。使用copy模块,copy.deepcopy()实现。copy.copy()则是浅复制。
5. 函数形参与实参的关系,默认参数值
def func(a=[]):
'''将列表a修改,并返回'''
a.append('end')
return a
l1 = ['1', '2', '3']
l2 = func(l1)
# l1也被修改了, l1 == l2 == ['1', '2', '3', 'end']
#使用形参a的默认值:
l3 = func()
# l3 == ['end']
l4 = func()
# l4 == ['end', 'end']
l5 = func()
# l5 == ['end', 'end', 'end']
# 发现,默认参数值a = [],好像记住了之前的值,每次默认参数变成了记住的新的参数。
l6 = func([1])
# l6 == [1, 'end']
# 不使用默认值时,又恢复了正常。
print(func().__defaults__)
# ['end', 'end', 'end']
函数形参:是函数内的局部变量。但是,实参幅值给形参时,
变量是附加在对象上的标识
所以,形参只是实参对象的引用,和实参绑定到相同的对象。函数内部对形参修改,会影响外部的实参对象。
函数形参的默认值:若形参有默认值,默认值在定义函数时计算,即运行到def语句时。并且,默认值会存入函数的 __defaults__
属性中。这也就是上面代码中,l4, l5, l6记住了之前结果的原因。
如果默认值是个可变对象(列表),且函数内会修改该默认形参,则__defaults__
中会一直储存此修改,引起意外的行为。
函数的默认参数,一般使用不可变对象,如
None
,单例值。