- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议29:区别对待可变对象和不可变对象
Python中一切皆对象,每一个对象都有一个唯一的标示符(id())、类型(type())以及值。对象根据其值能否修改分为可变对象和不可变对象,其中数字、字符串、元组属于不可变对象,字典以及列表、字节数组属于可变对象。而“菜鸟”常常会试图修改字符串中某个字符。看下面这个例子:
teststr = "I am a pytlon string" teststr[11]='h' print teststr
字符串为不可变对象,任何对字符串中某个字符的修改都会抛出异常。修改字符串中某个字符可以采用如下方式:
>>> teststr = "I am a pytlon string" >>> import array >>> a = array.array('c',teststr) >>> a[10]='h' >>> a array('c', 'I am a Python string') >>> a.tostring() 'I am a Python string'
再来看下面一段程序:
class Student(object): def __init__(self,name,course=[]): self.name=name self.course=course def addcourse(self,coursename): self.course.append(coursename) def printcourse(self): for item in self.course: print item stuA=Student("Wang yi") stuA.addcourse("English") stuA.addcourse("Math") print stuA.name +"'s course:" stuA.printcourse() print "-----------------------" stuB=Student("Li san") stuB.addcourse("Chinese") stuB.addcourse("Physics") print stuB.name +"'s course:" stuB.printcourse()
上述代码清单描述的是两个不同的学生选择不同课程的场景。这个程序有什么问题吗?通过运行程序得到如下输出结果:
Wang yi's course: English Math ----------------------- Li san's course: English Math Chinese Physics
你会诧异地发现Li san同学所选的多了English和Math两门课程。这是怎么回事呢?通过查看id(stuA.course)和id(stuB.course),我们发现这两个值是一样的,也就是说在内存中指的是同一块地址,但stuA和stuB本身却是两个不同的对象。在实例化这两个对象的时候,这两个对象被分配了不同的内存空间,并且调用init()函数进行初始化。但由于init()函数的第二个参数是个默认参数,默认参数在函数被调用的时候仅仅被评估一次,以后都会使用第一次评估的结果,因此实际上对象空间里面course所指向的是list的地址,每次操作的实际上是list所指向的具体列表。这是我们在将可变对象作为函数默认参数的时候要特别警惕的问题,对可变对象的更改会直接影响原对象。要解决上述例子中的问题,最好的方法是传入None作为默认参数,在创建对象的时候动态生成列表。具体代码如下:
def __init__(self,name,course=None): self.name=name if course is None:course=[] self.course=course
对于可变对象,还有一个问题是需要注意的。我们通过以下例子来说明:
>>> list1=['a','b','c'] >>> list2 = list1 >>> list1.append('d') >>> list1 ['a', 'b', 'c', 'd'] >>> list2 ①list2 也会发生变化 ['a', 'b', 'c', 'd'] >>> list3 = list1[:] ②切片操作相当于浅拷贝 >>> list3.remove('a') >>> list3 ['b', 'c', 'd'] >>> list1 ['a', 'b', 'c', 'd'] >>> list2 ['a', 'b', 'c', 'd'] >>> id(list3) ③重新指向一块内存 14075304 >>> id(list1) 13418864 >>> id(list2) 13418864
上面的例子中对list1的切片操作实际会重新生成一个对象,因为切片操作相当于浅拷贝,因此对list3的操作并不会改变list1和list2本身。我们再来看以下不可变对象的简单例子:
a=1 a+=2 print a
我们会发现此时a的值变为3,这是理所当然的,可是仔细一想a是属于数值类型,是不可变对象,怎么会发生改变呢?实际上Python中变量a存放的是数值1在内存中的地址,数值1本身才是不可变对象。在上面的过程中所改变的是a所指向的对象的地址,数值1并没有发生改变,当执行a+=2的时候重新分配了一块内存地址存放结果,并将a的引用改为该内存地址,而对象1所在的内存空间会最终被垃圾回收器回收。上述分析的图示如图3-5所示。
图3-5 a在内存中的变化示意图
通过id()函数分析也可以证实上述变化过程。
>>> a=1 >>> id(a) 12184696 >>> id(1) ①id(a) 和id(1) 的值并不相等 12184696 >>> a+=2 >>> a 3 >>> id(a) ②id(a) 的值发生改变 12184672 >>> id(3) ③id(a) 和id(3) 的值相同 12184672
id()函数分析也可以证实上述变化过程,对于不可变对象来说,当我们对其进行相关操作的时候,Python实际上仍然保持原来的值而是重新创建一个新的对象,所以字符串对象不允许以索引的方式进行赋值,当有两个对象同时指向一个字符串对象的时候,对其中一个对象的操作并不会影响另一个对象。
>>> str1 = "hello world" >>> str2 = str1 >>> str1 = str1[:-5] >>> str1 'hello ' >>> str2 'hello world' >>>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论