返回介绍

共享引用

发布于 2024-01-29 22:24:17 字数 3145 浏览 0 评论 0 收藏 0

到现在为止,我们已经看到了单个变量被赋值引用了多个对象的情况。现在,在交互模式下,引入另一个变量,并看一下变量名和对象的变化:

输入这两行语句后,生成如图6-2所示的结果。就像往常一样,第二行会使Python创建变量b。变量a正在使用,并且它在这里没有被赋值,所以它被替换成其引用的对象3,从而b也成为这个对象的一个引用。实际的效果就是变量a和b都引用了相同的对象(也就是说,指向了相同的内存空间)。这在Python中叫做共享引用——多个变量名引用了同一个对象。

图 6-2 运行赋值语句b=a之后的变量名和对象。变量b成为对象3的一个引用。在内部,变量实际上是一个指针指向了对象的内存空间,该内存空间是通过运行常量表达式3创建的

下一步,假设运行另一个语句扩展了这样的情况:

对于所有的Python赋值语句,这条语句简单地创建了一个新的对象(代表字符串值'spam'),并设置a对这个新的对象进行引用。尽管这样,这并不会改变b的值,b仍然引用原始的对象——整数3。最终的引用结构如图6-3所示。

图 6-3 最终运行完赋值语句a='spam'后的变量名和对象。变量a引用了由常量表达式'spam'所创建的新对象(例如,内存空间),但是变量b仍然引用原始的对象3。因为这个赋值运算改变的不是对象3,仅仅改变了变量a,变量b并没有发生改变

如果我们把变量b改成'spam'的话,也会发生同样的事情:赋值只会改变b,不会对a有影响。发生这种现象,跟没有类型差异一样。例如,思考下面这三条语句:

在这里,产生了同样的结果:Python让变量a引用对象3,让b引用与a相同的对象,如图6-2所示。之前,最后的那个赋值将a设置为一个完全不同的对象(在这种情况下,整数5是表达式“+”的计算结果)。这并不会产生改变了b的副作用。事实上,是没有办法改变对象3的值的:就像第4章所介绍过的,整数是不可变的,因此没有方法在原处修改它。

认识这种现象的一种方法就是,不像其他的一些语言,在Python中,变量总是一个指向对象的指针,而不是可改变的内存区域的标签:给一个变量赋一个新的值,并不是替换了原始的对象,而是让这个变量去引用完全不同的一个对象。实际的效果就是对一个变量赋值,仅仅会影响那个被赋值的变量。当可变的对象以及原处的改变进入这个场景,那么这个情形会有某种改变。想知道是怎样一种变化的话,请继续学习。

共享引用和在原处修改

正如你在这一部分后边的章节将会看到的那样,有一些对象和操作确实会在原处改变对象。例如,在一个列表中对一个偏移进行赋值确实会改变这个列表对象,而不是生成一个新的列表对象。对于支持这种在原处修改的对象,共享引用时的确需要加倍的小心,因为对一个变量名的修改会影响其他的变量。

为了进行深入地理解,让我们再看一看在第4章介绍过的列表对象。回忆一下列表,它在方括号中进行编写,是其他对象的简单集合,它支持对位置的在原处的赋值:

L1是一个包含了对象2、3和4的列表。在列表中的元素是通过它们的位置进行读取的,所以L1[0]引用对象2,它是列表L1中的第一个元素。当然,列表自身也是对象,就像整数和字符串一样。在运行之前的两个赋值后,L1和L2引用了相同的对象,就像我们之前例子中的a和b一样(如图6-2所示)。如果我们现在像下面这样去扩展这个交互:

L1直接设置为一个不同的对象,L2仍是引用最初的列表。尽管这样,如果我们稍稍改变一下这个语句的内容,就会有明显不同的效果。

在这里,没有改变L1,改变了L1所引用的对象的一个元素。这类修改会覆盖列表对象中的某部分。因为这个列表对象是与其他对象共享的(被其他对象引用),那么一个像这样在原处的改变不仅仅会对L1有影响。也就是说,必须意识到当做了这样的修改,它会影响程序的其他部分。在这个例子中,也会对L2产生影响,因为它与L1都引用了相同的对象。另外,我们实际上并没有改变L2,但是它的值将发生变化,因为它已经被修改了。

这种行为通常来说就是你所想要的,应该了解它是如何运作的,让它按照预期去工作。这也是默认的。如果你不想要这样的现象发生,需要Python拷贝对象,而不是创建引用。有很多种拷贝一个列表的办法,包括内置列表函数以及标准库的copy模块。也许最常用的办法就是从头到尾的分片(请查阅第4章和第7章有关分片的更多内容)。

这里,对L1的修改不会影响L2,因为L2引用的是L1所引用对象的一个拷贝。也就是说,两个变量指向了不同的内存区域。

注意这种分片技术不会应用在其他的可变的核心类型(字典和集合,因为它们不是序列)上,复制一个字典或集合应该使用X.copy()方法调用。而且,注意标准库中的copy模块有一个通用的复制任意对象类型的调用,也有一个拷贝嵌套对象结构(例如,嵌套了列表的一个字典)的调用:

我们将会在第8章和第9章更深入了解列表和字典,并复习共享引用和拷贝的概念。这里记住有些对象是可以在原处改变的(即可变的对象),这种对象往往对这些现象总是很开放。在Python中,这种对象包括了列表、字典以及一些通过class语句定义的对象。如果这不是你期望的现象,可以根据需要直接拷贝对象。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文