- 译者序
- 前言
- 第1章 问答环节
- 第2章 Python 如何运行程序
- 第3章 如何运行程序
- 第4章 介绍 Python 对象类型
- 第5章 数字
- 第6章 动态类型简介
- 第7章 字符串
- 第8章 列表与字典
- 第9章 元组、文件及其他
- 第10章 Python 语句简介
- 第11章 赋值、表达式和打印
- 第12章 if 测试和语法规则
- 第13章 while 和 for 循环
- 第14章 迭代器和解析,第一部分
- 第15章 文档
- 第16章 函数基础
- 第17章 作用域
- 第18章 参数
- 第19章 函数的高级话题
- 第20章 迭代和解析,第二部分
- 第21章 模块:宏伟蓝图
- 第22章 模块代码编写基础
- 第23章 模块包
- 第24章 高级模块话题
- 第25章 OOP:宏伟蓝图
- 第27章 更多实例
- 第28章 类代码编写细节
- 第29章 运算符重载
- 第30章 类的设计
- 第31章 类的高级主题
- 第32章 异常基础
- 第34章 异常对象
- 第35章 异常的设计
- 第36章 Unicode 和字节字符串
- 字符串基础知识
- Python 的字符串类型
- 文本和二进制文件
- Python 3.0 中的字符串应用
- 转换
- 编码 Unicode 字符串
- 编码非ASCII文本
- 编码和解码非ASCII文本
- 其他 Unicode 编码技术
- 转换编码
- 在 Python 2.6 中编码 Unicode 字符串
- 源文件字符集编码声明
- 使用 Python 3.0 Bytes 对象
- 序列操作
- 创建 bytes 对象的其他方式
- 混合字符串类型
- 使用 Python 3.0(和 Python 2.6)bytearray 对象
- 使用文本文件和二进制文件
- Python 3.0 中的文本和二进制模式
- 类型和内容错误匹配
- 使用 Unicode 文件
- 在 Python 3.0 中处理 BOM
- Python 2.6 中的 Unicode 文件
- Python 3.0 中其他字符串工具的变化
- Struct二进制数据模块
- pickle对象序列化模块
- XML解析工具
- 本章小结
- 本章习题
- 习题解答
- 第37章 管理属性
- 第38章 装饰器
- 第39章 元类
- 附录A 安装和配置
- 附录B 各部分练习题的解答
- 作者介绍
- 封面介绍
共享引用
输入这两行语句后,生成如图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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论