python对象引用和可变性

变量不是盒子

人们经常使用“变量是盒子”这样的比喻,但是这有碍于理解面向对象语言中的引用式变量。
Python变量类似于Java中的引用式变量,因此最好把它们理解为附加在对象上的标注。

#示例1:变量a和b引用同一个列表,而不是那个列表的副本
>>> a = [1, 2, 3] 
>>> b = a 
>>> a.append(4) 
>>> b 
[1, 2, 3, 4]

在这里插入图片描述
如果把变量想成一个盒子,那么无法解释python中的赋值,应该把变量视作便利贴,这样示例1中的行为就能得到解释了。

对引用式变量来说,说把变量分配给对象更合理,反过来说就有问题。毕竟,对象在赋值之前就创建了。比如:讲到seesaw对象时,会说“把变量s分配给seesaw”,绝不会说“把seesaw分配给变量s”。

下面示例2证明赋值语句的右边先执行。

创建对象之后才会把变量分配给对象
>>> class Gizmo: 
...    def __init__(self): 
...         print('Gizmo id: %d' % id(self)) 
... 
>>> x = Gizmo() 
Gizmo id: 4301489152  #➊ 
>>> y = Gizmo() * 10  #➋ 
Gizmo id: 4301489432  #➌ 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int'
>>> 
>>> dir()  #➍ 
['Gizmo', '__builtins__', '__doc__', '__loader__', '__name__', 
'__package__', '__spec__', 'x']

➊ 输出的Gizmo id: …是创建Gizmo实例的副作用。
➋ 在乘法运算中使用Gizmo实例会抛出异常。
➌ 这里表明,在尝试求积之前其实会创建一个新的Gizmo实例。
➍ 但是,肯定不会创建变量y,因为在对赋值语句的右边进行求值时抛出了异常。

为了理解Python中的赋值语句,应该始终先读右边。对象在右边创建或获取,在此之后左边的变量才会绑定到对象上,这就像为对象贴上标注。

标识、相等性和别名

#示例3:charles和lewis指代同一个对象
>>> charles = {'name': 'Charles L. Dodgson', 'born': 1832} 
>>> lewis = charles  #➊ 
>>> lewis is charles 
True 
>>> id(charles), id(lewis)  #➋ 
(3024388455088, 3024388455088) 
>>> lewis['balance'] = 950  #➌ 
>>> charles 
{'name': 'Charles L. Dodgson', 'balance': 950, 'born': 1832}

➊ lewis是charles的别名。
➋ is运算符和id函数确认了这一点。
➌ 向lewis中添加一个元素相当于向charles中添加一个元素。

#示例4:alex与charles比较的结果是相等,但alex不是charles
>>> alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}  #➊ 
>>> alex == charles  #➋ 
True 
>>> alex is not charles  #➌ 
True
>>>id(charles),id(alex)
(3024388455088, 3024387755464)

➊ alex指代的对象与赋值给charles的对象内容一样
➋ 比较两个对象,结果相等,这是因为dict类的__eq__方法就是这样实现的。
但它们是不同的对象。这是Python说明标识不同的方式:a is not b
在这里插入图片描述
charles和alex绑定同一个对象,alex绑定另一个具有相同内容的对象。

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

每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变;你可以把标识理解为对象在内存中的地址。is运算符比较两个对象的标识;id()函数返回对象标识的整数表示。

对象ID的真正意义在不同的实现中有所不同。在CPython中,id()返回对象的内存地址,但是在其他Python解释器中可能是别的值。关键是,ID一定是唯一的数值标注,而且在对象的生命周期中绝不会变。

其实,编程中很少使用id()函数。标识最常使用is运算符检查,而不是直接比较ID

在==和is之间选择

==运算符比较两个对象的值(对象中保存的数据),而is比较对象的标识。通常,我们关注的是值,而不是标识,因此Python代码中==出现的频率比is高。

然而,在变量和单例值之间比较时,应该使用is。目前,最常使用is检查变量绑定的值是不是None。

下面是推荐的写法:
x is None
否定的写法:
x is not None

is运算符比==速度快,因为它不能重载,所以Python不用寻找并调用特殊方法,而是直接比较两个整数ID。而a == b是语法糖,等同于a.eq(b)。继承自object的__eq__方法比较两个对象的ID,结果与is一样.

元组的相对可变性

元组与多数Python集合(列表、字典、集,等等)一样,保存的是对象的引用。1如果引用的元素是可变的,即便元组本身不可变,元素依然可变。

也就是说,元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关

下面的示例5中元组的值会随着引用的可变对象的变化而变。元组中不可变的是元素的标识。

#示例5:一开始,t1和t2相等,但是修改t1中的一个可变元素后,二者不相等了
>>> 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

➊ t1不可变,但是t1[-1]可变。
➋ 构建元组t2,它的元素与t1一样。
➌ 虽然t1和t2是不同的对象,但是二者相等——与预期相符。
➍ 查看t1[-1]列表的标识。
➎ 就地修改t1[-1]列表。
➏ t1[-1]的标识没变,只是值变了。
➐ 现在,t1和t2不相等。

复制对象时,相等性和标识之间的区别有更深入的影响。副本与源对象相等,但是ID不同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值