1 模块简介

当你开始使用Python编程时,你或许已经使用了iterators(迭代器)和generators(生成器),你当时可能并没有意识到。在本篇博文中,我们将会学习迭代器和生成器是什么。当然,我们也会了解如何创建它们,在我们需要的时候,就可以创建属于我们自己的迭代器和生成器。

2 模块使用

2.1 迭代器

迭代器是一个允许你在一个容器上进行迭代的对象。Python的迭代器主要通过两个方法实现:__iter__和__next__。__iter__要求你的容器支持迭代。它会返回迭代器对象本身。如果你想创建一个迭代器对象,你还需要定义__next__方法,它将会返回容器的下一个元素。

注意: 在Python 2中,命名习惯有所不同,__iter__保持不变,__next__ 改为next。

为了对这些概念更加清晰,让我们回顾下面的两个定义:

  • 可迭代对象(iterable),只定义了__iter__方法;
  • 迭代器(iterator),定义了__iter__和__next__两个方法,__iter__返回迭代器本身,__next__方法返回下一个元素;

所有函数名中有双下划线的方法,都很神奇,你不需要直接调用__iter__或者__next__。你可以使用for循环或者转换为列表,Python就会自动替你调用这些方法。当然你或许还是想调用它们,那么你可以使用Python内置的函数iter和next方法。

Python 3 提供了一些序列类型,例如list、tuple和range。list是一个可迭代对象,但不是一个迭代器,因为它没有实现__next__方法。我们通过下面的例子,可以很容易发现这点。

>>> my_list = [1,2,3]
>>> next(my_list)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator

当我们在这个例子中,尝试着调用列表的next方法,最终我们接收到一个TypeError异常信息,可以发现列表对象并不是一个迭代器。我们换一个思路,让我们看下面的代码:

>>> iter(my_list)
<list_iterator object at 0x7f6ec6b00f98>
>>> list_iterator = iter(my_list)
>>> next(list_iterator)
1
>>> next(list_iterator)
2
>>> next(list_iterator)
3
>>> next(list_iterator)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

仅仅对列表调用Python内置的iter函数,就可以将列表转换为迭代器。当你对它调用next方法,直到它遍历完所有元素,就会收到StopIteration异常信息。让我们尝试将列表转换为迭代器,然后在循环中遍历它。

>>> for item in iter(my_list):
... print(item)
...
1
2
3

当你使用循环来遍历一个迭代器,你就不需要调用next方法,你也无需担心会收到StopIteration异常信息。

2.2 创建属于你的迭代器

有时候,你也想创建属于你的迭代器。Python很容易地就可以完成这个任务。正如上一章节提到,你需要做的就是在你的类里实现__iter__和__next__这两个方法。让我们创建一个迭代器,这个迭代器可以遍历字符串。

class MyIterator:
def __init__(self,letters):
self.letters = letters
self.position = 0
def __iter__(self):
return self
def __next__(self):
if self.position >= len(self.letters):
raise StopIteration
letter = self.letters[self.position]
self.position += 1
return letter if __name__ == "__main__":
i = MyIterator('abcd')
for item in i:
print (item)

对于这个例子,在我们的类中,我们仅仅需要实现三个方法。在初始化函数中,我们传入字符串,创建一个类变量用于引用这个字符串。我们也初始化了一个位置变量,以便于我们知道我们在字符串的位置。__iter__方法仅仅返回它本身,这就是是它所要做的。__next__是这个类中最重要的方法,我们首先检查位置是否超出字符串的长度,如果超出,就会抛出StopIteration异常信息。然后,我们提取出字符串上该位置的字符,将位置加1,最后返回该字符。

下面的例子中,我们创建一个无限的迭代器。一个无限的迭代器就是可以一直遍历。你要注意的是当调用这个迭代器时,如果你不加任何限制的话,它们将会导致死循环。

class Doubler():
def __init__(self):
self.number = 0
def __iter__(self):
return self
def __next__(self):
self.number += 1
return self.number * self.number if __name__ == "__main__":
doubler = Doubler()
count = 0
for number in doubler:
print (number)
if count > 5:
break
count += 1

在这段代码中,我们没有向迭代器中传入任何参数。我们仅仅以此为例进行说明。为了不陷入死循环,我们在开始遍历迭代器之前,加入一个计数器。最后,我们开始遍历,当计时器超过5,循环就会中断。

2.3 生成器

一个普通的Python函数经常返回一个值,无论它是列表、整数还是其他对象。但是如果你想调用一个函数,这个函数能否产生一系列值呢?这个就是生成器诞生的原因。生成器的工作机制是保存它所停止(或者说它已经产生)的位置,然后给主调函数返回一个值。不是返回一个调用函数的执行,生成器仅仅返回一个临时的控制返回。为了完成这个功能,生成器函数需要使用Python的 yield 语句。

注意: 在其它编程语言中,生成器可能叫做协同程序;

让我们先创建一个简单的生成器,

>>> def doubler_generator():
... number = 2
... while True:
... yield number
... number *= number
...
>>> doubler = doubler_generator()
>>> next(doubler)
2
>>> next(doubler)
4
>>> next(doubler)
16
>>> type(doubler)
<class 'generator'>

这个生成器仅仅是创建一个无限的序列。你可以一直对它调用next方法,它绝不会用完所有的值。因为你可以遍历生成器,因此,一个生成器也可以认为是一类迭代器,但是没有人会真正把它们联系在一起。其实,生成器也定义了__next__方法,我们观察上面的例子,这就是为什么next函数可以正常执行。

让我们来看另一个例子,这个例子中只产生3个元素,而不是无限的序列。

>>> def silly_generator():
... yield "Python"
... yield "Rocks"
... yield "So do you!"
...
>>> gen = silly_generator()
>>> next(gen)
'Python'
>>> next(gen)
'Rocks'
>>> next(gen)
'So do you!'
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

在这个例子中,我们创建了一个使用yield语句3次的生成器。在每个实例中,它产生不同的字符串。你可以认为yield是生成器的返回语句。无论何时你调用yield,函数都会中止,然后保存它的状态。然后它产生出值,这就是你在上面的例子里通过终端看到的输出。如果我们在函数中有一些变量,那么这些变量也会被保存。

当你看到StopIteration,你应该了解到你已经耗尽了这个迭代器。这就意味着这个迭代器已经用完了。你也可以在上面的迭代器部分看到这个特点。

无论何时我们调用next方法,生成器在它上次离开的地方开始,然后产生下一个值或者我们结束函数,生成器结束。如果你一直不调用next方法,这个状态就会永远保持下去。

让我们实例化这个生成器,然后使用循环来遍历它。

>>> gen = silly_generator()
>>> for item in gen:
... print(item)
...
Python
Rocks
So do you!

我们创建一个新的生成器实例,是因为当我们遍历完这个生成器的时候,我们不希望产生任何事情。这是因为我们已经遍历完这个生成器的实例的所有值。所以在这个例子中,我们创建一个实例,然后通过循环来遍历它,并打印产生出的值。当生成器遍历完,for循环会替我们处理StopIteration异常,然后中断循环。

生成器的一个优点就是它可以遍历很大的数据集,然后一次只返回一部分数据。下面的例子就是当我们打开一个文件,然后逐行返回数据,

with open("temp.txt") as fobj:
for line in fobj:
print line

当我们以这种方式遍历时,Python会将文件对象转换为一个生成器。这种方式允许我们处理空间太大以至于无法读入到内存的文件。对于任意的大数据集,对于这些大数据集,你需要块操作或者当你需要生成一个大数据集但是它将会填满你的内存,你就会发现生成器很有用处。

2.4 总结

到这里,你已经了解了什么是迭代器以及如何使用它。你也了解到可迭代对象和迭代器的区别。最后,我们学习了什么是生成器以及你如何使用它。例如,生成器很适合处理内存有效的数据。

3 Reference

Python 201

Python标准模块--Iterators和Generators的更多相关文章

  1. Python标准模块--threading

    1 模块简介 threading模块在Python1.5.2中首次引入,是低级thread模块的一个增强版.threading模块让线程使用起来更加容易,允许程序同一时间运行多个操作. 不过请注意,P ...

  2. Python标准模块--logging

    1 logging模块简介 logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级.日志保存路径.日志文件回滚等:相比print,具备如下优点: 可以通过设置不同 ...

  3. Python标准模块--importlib

    作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 模块简介 Python提供了importlib包作为标准库的一 ...

  4. Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures

    参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...

  5. python 全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)

    昨日内容回顾 线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的 一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的在当 ...

  6. 【转】Python标准模块--importlib

    [转]Python标准模块--importlib 作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 模块简介 P ...

  7. Python标准模块--logging(转载)

    转载地址:http://www.cnblogs.com/zhbzz2007/p/5943685.html#undefined Python标准模块--logging 1 logging模块简介 log ...

  8. python全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)

    昨日内容回顾 线程 什么是线程? 线程是cpu调度的最小单位 进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的 ...

  9. python标准模块(二)

    本文会涉及到的模块: json.pickle urllib.Requests xml.etree configparser shutil.zipfile.tarfile 1. json & p ...

随机推荐

  1. 旺财速啃H5框架之Bootstrap(四)

    上一篇<<旺财速啃H5框架之Bootstrap(三)>>已经把导航做了,接下来搭建内容框架.... 对于不规整的网页,要做成自适应就有点玩大了.... 例如下面这种版式的页面. ...

  2. SQLServer执行命令出现“目录无效的提示”

    异常处理汇总-数据库系列  http://www.cnblogs.com/dunitian/p/4522990.html 一般都是清理垃圾清理过头了,把不该删的目录删了 网上说法: 问题描述: 1.s ...

  3. C#中如何给Excel添加水印

    我们知道Microsoft Excel并没有内置的功能直接给Excel表添加水印,但是其实我们可以用其他变通的方式来解决此问题,如通过添加页眉图片或艺术字的方法来模仿水印的外观.所以在这篇文章中,我将 ...

  4. PAT练习题目录

    点题号就能查看题解了,另外代码也放在了开源中国码云上: 甲级:代码集合:https://git.oschina.net/firstmiki/PAT-Advanced-Level-Practise 10 ...

  5. java面向对象中的关键字

    1,super关键字 super:父类的意思 1. super.属性名 (调用父类的属性) 2. super.方法名 (调用父类的方法) 3. super([参数列表])(调用父类的构造方法) 注意: ...

  6. mac下生成ssh keys 并上传github仓储

    使用github仓储需要本机生成一个公钥key 添加到自己的git账户SSH keys中   mac 生成方法:   1. 打开终端 输入   ssh-keygen 然后系统提示输入文件保存位置等信息 ...

  7. 借助 SIMD 数据布局模板和数据预处理提高 SIMD 在动画中的使用效率

    原文链接 简介 为发挥 SIMD1 的最大作用,除了对其进行矢量化处理2外,我们还需作出其他努力.可以尝试为循环添加 #pragma omp simd3,查看编译器是否成功进行矢量化,如果性能有所提升 ...

  8. 微信小程序之用户数据解密(七)

    [未经作者本人同意,请勿以任何形式转载] 经常看到有点的小伙伴在群里问小程序用户数据解密流程,所以打算写一篇关于小程序用户敏感数据解密教程: 加密过程微信服务器完成,解密过程在小程序和自身服务器完成, ...

  9. 如何安装并简单的使用OwinHost——Katana

    微软OWIN的提出必然会引起一场风暴,而我们作为C#阵营中一份子,自然免不了会卷入其中.OWIN是什么东西,我在这里就不解析了,还不知道是OWIN是什么的读者请打开浏览器,然后搜索即可,中文的英文的应 ...

  10. linux用户权限相关内容查看

    linux用户权限相关内容查看 1   用户信息 创建用户一个名为 webuser 的账号,并填写相应的信息: root@iZ94fabhqhuZ:~# adduser webuser Adding ...