- 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章 性能剖析与优化
建议65:熟悉 Python 的迭代器协议
其实对于大部分Python程序员而言,迭代器的概念可能并不熟悉。但这很正常,与C++等语言不同,Python的迭代器集成在语言之中,与语言完美地无缝集成,不像C++中那样需要专门去理解这一个概念。比如,要遍历一个容器,Python代码如下:
>>> alist = range(2) >>> for i in alist: ... print i 0 1
而C++代码如下:
using namespace std; vector<int> myIntVector; // 往容器 myIntVector 中添加元素的操作,略 for(vector<int>::iterator = myIntVector.begin(); myIntVectorIterator != myIntVector.end(); myIntVectorIterator++){ cout<<*myIntVectorIterator<<" "; }
两相对比,可以看到C++的代码中,多了一个vector<int>::iterator类型,它是什么、有什么用、什么时候用、怎么用,都是C++程序员需要理解和掌握的内容,所以可以说,在“实现遍历容器”这一事情上,使用C++要付出更多的精力去学习更多的内容,这就是Python把迭代器内建在语言之中的好处。
但是,并非所有的时候都能够隐藏细节,特别是在写一本书向读者讲述其中的机理的时候。所以在这里,首先需要向大家介绍一下iter()函数。iter()可以输入两个实参,但为了简化起见,在这里忽略第二个可选参数,只介绍一个参数的形式。iter()函数返回一个迭代器对象,接受的参数是一个实现了__iter__()方法的容器或迭代器(精确来说,还支持仅有__getitem__()方法的容器)。对于容器而言,__iter__()方法返回一个迭代器对象,而对迭代器而言,它的__iter__()方法返回其自身,所以如果我们用一个迭代器对象it,当以它为参数调用iter(it)时,返回的是自身。
>>> it = iter(alist) >>> it2 = iter(it) >>> assert id(it) == id(it2)
到时此,就可以跟大家讲一下迭代器协议了。前文已经说过,所谓协议,是一种松散的约定,并没有相应的接口定义,所以把协议简单归纳如下:
1)实现__iter__()方法,返回一个迭代器。
2)实现next()方法,返回当前的元素,并指向下一个元素的位置,如果当前位置已无元素,则抛出StopIteration异常。
可以通过以下代码验证这个协议:
>>> alist = range(2) >>> it = alist.__iter__() >>> it.next() 0 >>> it.next() 1 >>> it.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
与上例使用iter()内置函数不同,这次的代码是it=alist.__iter__(),可见list这一容器确实是实现了迭代器协议中容器的部分。后续连续3次的next()方法调用也印证了协议的第2条。
熟悉了迭代器协议,那么我们就可以使用它来遍历所有的容器,仍然以list对象为例。
>>> alist = range(2) >>> it = iter(alist) >>> while True: ... try: ... print it.next() ... except StopIteration: ... break ... 0 1
可以看到输出跟最初使用for循环是一样的,对,你的灵光一闪没有错,其实for语句就是对获取容器的迭代器、调用迭代器的next()方法以及对StopIteration进行处理等流程进行封装的语法糖(类似的语法糖还有in/not it语句)。
迭代器最大的好处是定义了统一的访问容器(或集合)的统一接口,所以程序员可以随时定义自己的迭代器,只要实现了迭代器协议就可以。除此之外,迭代器还有惰性求值的特性,它仅可以在迭代至当前元素时才计算(或读取)该元素的值,在此之前可以不存在,在此之后可以销毁,也就是说不需要在遍历之前事先准备好整个迭代过程中的所有元素,所以非常适合遍历无穷个元素的集合(如斐波那契数列)或巨大的事物(如文件)。
class Fib(object): def __init__(self): self._a = 0 self._b = 1 def __iter__(self): return self def next(self): self._a, self._b = self._b, self._a + self._b return self._a for i, f in enumerate(Fib()): print f if i > 10: break
这段代码能够打印斐波那契数列的前10项。再来看一下传统的使用容器存储整个数列的方案。
>>> def fib(n): """ 返回小于指定值的斐波那契数列""" result=[] a,b=0,1 while b<n: result.append(b) a,b=b,a+b return result >>> fib(10) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
与直接使用容器的代码相比,它仅使用两个成员变量,显而易见更省内存,并在一些应用场景更省CPU计算资源,所以在编写代码中应当多多使用迭代器协议,避免劣化代码。对于这一观点,不必怀疑,从Python 2.3版本开始,itertools成为了标准库的一员已经充分印证这个观点。
itertools的目标是提供一系列计算快速、内存高效的函数,这些函数可以单独使用,也可以进行组合,这个模块受到了Haskell等函数式编程语言的启发,所以大量使用itertools模块中的函数的代码,看起来有点像函数式编程语言写推荐,比如sum(imap(operator.mul, vector1,vector2))能够用来运行两个向量的对应元素乘积之和。
itertools最为人所熟知的版本,应该算是zip、map、filter、slice的替代,izip(izip_longest)、imap(startmap)、ifilter(ifilterfalse)、islice,它们与原来的那几个内置函数有一样的功能,只是返回的是迭代器(在Python 3中,新的函数撤底替换掉了旧函数)。
除了对标准函数的替代,itertools还提供以下几个有用的函数:chain()用以同时连续地迭代多个序列;compress()、dropwhile()和takewhile()能用以遴选序列元素;tee()就像同名的UNIX应用程序,对序列作n次迭代;而groupby的效果类似SQL中相同拼写的关键字所带的效果。
[k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
除了这些针对有限元素的迭代帮助函数之外,还有count()、cycle()、repeat()等函数产生无穷序列,这3个函数就分别可以产生算术递增数列、无限重复实参序列的序列和重复产生同一个值的序列。
如果以上这些函数让你感到吃惊,那么接下来的4个组合数学的函数就会让你更加惊讶了,如表6-3所示。
表6-3 组合函数以及其意义
下面通过例子来熟悉一下,第一行是代码,第二行是结果。
product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC combinations('ABCD', 2) AB AC AD BC BD CD combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 其中 product() 可以接受多个序列,如: >>> for i in product('ABC', '123', repeat=2): print ''.join(i) ... A1A1 A1A2 A1A3 A1B1 A1B2 A1B3 # 略去其余输出
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论