- 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章 性能剖析与优化
建议9:数据交换值的时候不推荐使用中间变量
交换两个变量的值,大家熟悉的代码如下:
>>> temp = x >>> x = y >>> y = temp
实际上,在Python中还有更简单、更Pythonic的实现方式,代码如下:
>>> x,y = y,x
上面的实现方式不需要借助任何中间变量并且能够获取更好的性能。我们来简单测试一下。
>>> from timeit import Timer >>> Timer('temp = x;x = y;y = temp','x=2;y =3').timeit() 0.10689428530212189 >>> Timer('x,y = y,x','x=2;y=3').timeit() 0.08492583713832502
从测试结果可以看出,第二种方式耗费的时间更少,并且由于不需要借助中间变量,代码更为简洁,是值得推荐的一种方式。那么,为什么第二种方式可以做到更优呢?这要从Python表达式计算的顺序说起。一般情况下Python表达式的计算顺序是从左到右,但遇到表达式赋值的时候表达式右边的操作数先于左边的操作数计算,因此表达式expr3,expr4= expr1,expr2的计算顺序是expr1,expr2→expr3,expr4。因此对于表达式x,y=y,x,其在内存中执行的顺序如下:
1)先计算右边的表达式y,x,因此先在内存中创建元组(y,x),其标示符和值分别为y、x及其对应的值,其中y和x是在初始化时已经存在于内存中的对象。
2)计算表达式左边的值并进行赋值,元组被依次分配给左边的标示符,通过解压缩(unpacking),元组第一标识符(为y)分配给左边第一个元素(此时为x),元组第二个标识符(为x)分配给第二个元素(此时为y),从而达到x、y值交换的目的。
更深入一点我们从Python生成的字节码来分析。Python的字节码是一种类似汇编指令的中间语言,但是一个字节码指令并不是对应一个机器指令。我们通过以下dis模块的来进行分析:
>>> import dis >>> def swap1(): ... x =2 ... y =3 ... x, y = y, x ... >>> def swap2(): ... x =2 ... y =3 ... temp = x ... x = y ... y = temp ... >>> print 'swap1():' swap1(): >>> dis.dis(swap1) 2 0 LOAD_CONST 1 (2) 3 STORE_FAST 0 (x) 3 6 LOAD_CONST 2 (3) 9 STORE_FAST 1 (y) 4 12 LOAD_FAST 1 (y) 15 LOAD_FAST 0 (x) 18 ROT_TWO 19 STORE_FAST 0 (x) 22 STORE_FAST 1 (y) 25 LOAD_CONST 0 (None) 28 RETURN_VALUE >>> print 'swap2():' swap2(): >>> dis.dis(swap2) 2 0 LOAD_CONST 1 (2) 3 STORE_FAST 0 (x) 3 6 LOAD_CONST 2 (3) 9 STORE_FAST 1 (y) 4 12 LOAD_FAST 0 (x) 15 STORE_FAST 2 (temp) 5 18 LOAD_FAST 1 (y) 21 STORE_FAST 0 (x) 6 24 LOAD_FAST 2 (temp) 27 STORE_FAST 1 (y) 30 LOAD_CONST 0 (None) 33 RETURN_VALUE
通过字节码可以看出,区别主要集中在swap1函数的第4行和swap2函数的第4~6行代码,其中swap1的第4行代码对应的字节码中有2个LOAD_FAST指令、2个STORE_FAST指令和1个ROT_TWO指令,而swap2函数对应的第4~6行代码中共生成了3个LOAD_FAST指令和3个STORE_FAST指令。而指令ROT_TWO的主要作用是交换两个栈的最顶层元素,它比执行一个LOAD_FAST+STORE_FAST指令更快。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论