前戏:迭代器和生成器

迭代:

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上(用isinstance判断)

可以直接作用于for循环的对象统称为可迭代对象

可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;(称为容器<容器是一种把多个元素组织在一起的数据结构>,很多容器都是可迭代的)
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。

(一)迭代器

一个实现了__iter__方法的对象是可迭代的,一个实现了__next__方法的对象则是迭代器

对于序列和字典的可迭代,是因为在该对象中实现了上面的两个方法

__iter__方法会返回一个迭代器,而所谓的迭代器就是具有__next__方法的对象。在调用__next__方法时,迭代器会返回他的下一个值。若是next方法被调用牡丹石迭代器中没有值可以返回,就会引发一个StopIteration异常

迭代器的优点:需要数据就去获取,而不是一次获取全部数据

相对于我们一次性取出数据,放在列表等类型中,若数据量过大,那么列表会占据大量的内存。而且对于这些数据,我们若是只使用一次就释放的话,那么放在列表中实在是太过浪费内存。
更好的方法就是使用迭代器。迭代器只会取出当前需要的数据方法内存中。

例如Django中的queryset惰性机制中就有提及迭代器的好处(在处理大量的数据时)

栗子:不使用列表的案例,因为如果使用列表,那么列表长度将会是无穷大。占据空间将会是巨大的。

斐波那契数列:

class Fibs:
def __init__(self):
self.a =
self.b = def __next__(self):
self.a,self.b = self.b,self.a+self.b
return self.a def __iter__(self):
return self f = Fibs() for i in f:
if i > :
print(i) #
break

补充:内建函数iter可以从可迭代的对象中获取迭代器

>>> a = [,,,]
>>> b = iter(a)
>>> type(b)
<class 'list_iterator'>
>>> next(b) >>> next(b) >>> next(b) >>> next(b)
Traceback (most recent call last):
File "<stdin>", line , in <module>
StopIteration 可以知道迭代器是一次性消耗品(只会向前获取,不会向后获取),当耗尽时就会触发StopIteration异常
若是想保留一份数据,可以用deepcopy

从迭代器中获取序列:

class Fibs:
def __init__(self):
self.a =
self.b = def __next__(self):
self.a,self.b = self.b,self.a+self.b
if self.a > :
raise StopIteration
return self.a def __iter__(self):
return self f = Fibs() ls = list(f)
print(ls) #[, , , , , , , , , , , , , , , , ]

使用list构造方法显示的将迭代器转换为列表

class list(object):
def __init__(self, seq=()): # known special case of list.__init__
"""
list() -> new empty list
list(iterable) -> new list initialized from iterable's items
     (若是迭代器,那么新的列表则是迭代器的所有成员,结束是以StopIteration为标志,若是上面没有触发,那么会一直去扩展列表)
# (copied from class doc)
"""
pass

结束


应该还记得列表推导式(生成式)<顺道回忆下lambda表达式>

>>> [x for x in range() if x %  == ]
[, , , , , , , , , , , , , , ]

通过列表推导式,我们可以直接生成一个列表,同样的,这个列表的内存也是受到限制的,当我们使用列表推导式,一次生成一个超大数量的列表,会占据大量内存,然而,若我们只是访问了前面几个,那么后面的空间占用几乎是无用的。

for i in [x for x in range() if x %  == ]:
if i < :
print(i)
else:
break

在上面案例中,我们只是想去获取满足条件的数据的一部分。但是在进行循环时,并不会立刻进行,而是需要将列表生成式全部执行后,才允许去进行循环。而我们所需要的数据仅仅是列表中的前一部分,但是列表推导式一次性将数据全部生成。占据大量无用的空间。那么我们是否可以做到像迭代器那样,需要的时候再去获取。从而避免数据冗余

(二)生成器

生成器都是迭代器。生成器是一种用普通函数语法定义的迭代器

创建一个生成器方法有多种:

其中第一种与列表推导式十分相似,只是需要将中括号[]变为小括号()

>>> b = [x for x in range() if x %  == ]
>>> type(b)
<class 'list'>
[, , , , , , , , , , , , , , ] >>> b = (x for x in range() if x % == )
>>> type(b)
<class 'generator'>
>>> next(b) >>> next(b) >>> next(b) >>> for i in b:
... print(i) 当数据全部取出后也会触发StopIteration错误

另外一种是:任何包括yield语句的函数都可以称为生成器。

这里同样以斐波那契数列为例:

def fibs(max):
n,a,b = ,,
while n < max:
yield a  #yield语句
n +=
a, b = b, a+b f = fibs() for i in f:
print(i) #

生成器和普通函数的行为有很大的区别。

  1. 不像return返回一次结果就结束函数,而是可以返回多次结果。从什么for循环可以看出,这一个函数返回了不止一次结果
  2. 每产生一个值(即在yield语句中返回的值),函数就会被冻结,不在执行:即函数停在那点等待被重新唤醒。被重新唤醒后就从之前通知的那点开始执行
def 函数:
...
yield 执行第一次后返回值1后冻结,不在执行,等待第二次
... #执行第二次时会向下继续执行,直到下一次yield
...
...
yield #第二次冻结(这个过程包括了执行上面的逻辑语句)
...
...
...
yield

案例:

>>> def flatten(nested):
... for sublist in nested:
... for ele in sublist:
... yield ele >>> for num in flatten(nested):
... print(num)
... >>>

也可以同上面迭代器一样使用list显示转换为列表。

>>> list(flatten(nested))
[, , , , ]
>>>

但是这样会立刻实例化列表,丧失了迭代的优势。

def nrange(num):
temp = -
while True:
temp = temp +
if temp >= num:
return
else:
yield temp for i in nrange():
print(i)

含有return的自定义nrange生成器

通用生成器:

生成器是一个包含yield关键字的函数。当他被调用的时候,在函数体中的代码不会执行,而是会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句。(yield意味着生成一个值,并冻结执行,等待下一次执行。return意味着生成器要通知执行)

生成器由两部分组成:生成器的函数和生成器的迭代器。生成器的函数使用def语句定义的,包含yield的。生成器的迭代器是这个函数的返回部分。合在一起就是生成器。

生成器方法:

生成器中新特征:可以为生成器提供值,而不是只像上面那样生成器为外面返回值。生成器内外可以进行交流

外部作用域访问生成器的send方法,可以向生成器内部传递消息(任意对象),此时yield不再是一个返回值语句,而是一个表达式

send方法和yield语句的执行区别:

yield

def rep2(val):
yield val
print("aaa")
val +=
print("bbb")
yield val r = rep2() print(next(r))  #可以看出,执行yield返回值后,就冻结在该条语句,不再向下执行,只有当下一个next出现,才会继续执行
def rep(val):new = (yield val)  #接收send发送过来的数据
if new:
print(new)
new = (yield val)
if new:
print(new) f = rep()
v = next(f) f.send("dsad")  #会打印出来dasd  可以看出,当send发送数据后,在接收数据后,会继续向下执行,直到下一个yield表达式出现

send方法使用:

注意:在使用send方法时,只有当生成器挂起以后才有意义(也就是说:在yield函数第一次执行之后)

def rep(val):
new = (yield val)
if new:
print(new)
new = (yield val)
if new:
print(new)

开始执行:

若没有将生成器挂起:

TypeError: can't send non-None value to a just-started generator

所以,我们在使用时需要先挂起生成器。挂起方法有两种:

第一种:

r = rep()
v = next(r)  #这里正常执行next获取yield返回,后面就可以正常使用send
print(v) r.send("dsad")
r.send("dsadds")

第二种(由刚刚的TypeError可以知道不能send一个非None值,在第一次时,所以我们可以直接在第一次时send(None)):

r.send(None)
r.send("dsad")
r.send("dsadds")

两种方法,强烈推荐第二种

原因:使用send方法时,需要注意两点

1.需要先将生成器挂起,此时才有意义

2.send方法第一次使用时,也是需要进行一次next()方法执行,或者send(None)执行

第2条件是在我们使用send前有其他yield语句返回时,可以了解到

def rep(val):
yield val
new = (yield val)
if new:
print(new)
new = (yield val)
if new:
print(new) r = rep()
v = next(r)  #挂起生成器
print(v)
v = next(r)  #激活send方法
print(v)
r.send("dsad")
r.send("dsadds")
----------------
正常输出
#
#
#dsad
#dsadds
若是只是挂起了生成器,没有激活send方法,那么默认第一个send方法会拿去激活
r = rep()
v = next(r)
print(v) # v = next(r)  #没有去激活send方法
# print(v) r.send("dsad")  #第一个send方法会被用到去激活send
r.send("dsadds")    #这个才是正常的信息传入
----------

 #33
 #dsadds

所以我们最好使用send(None)表示去激活send方法,不易混淆

def rep(val):
yield val
new = (yield val)
if new:
print(new)
new = (yield val)
if new:
print(new) r = rep() v = next(r) #挂起生成器         也可以用send(None)去挂起生成器,但是还是不要这样做,两个套用容易混淆
print(v) r.send(None) #激活send方法(在首次使用send时使用) r.send("dsad")
r.send("dsadds")
--------------

 #33
 #dsad
 #dsadds

另外补充下send方法会获取到yield表达式中的返回值

def rep(val):
yield val
new = (yield val)
if new:
print(new)
new = (yield val)
if new:
print(new) r = rep()
v = next(r)
print(v) v = r.send(None)
print(v,)
v = r.send("dsad")
print(v,)
v = r.send("dsadds")  #在最后一个send方法时,没有返回值
print(v,)
----------------------- dsad dsadds

send方法会依次获取yield表达式的返回值,所以在第三个send方法使用时,并没有yield与之对应,所以没有值。

具体原因暂不讨论。

注意区分yield语句和yield表达式

生成器的定义是:包含yield语句的函数是生成器

再进一步讨论:send方法

 def rep(val):
yield val  #这里已经含有yield语句,此函数是生成器,我们在下面执行的next()只是正常执行这条语句
print("t1")
new = (yield val)
print("t2")
if new:
print("t3")
print(new)
new = (yield val)
if new:
print(new)
r = rep()

(1):正常执行yield语句(行2),返回值,并且冻结到行2,不在向下执行

v = next(r)
print(v)
-------
#33

那么如何执行到下面的yield表达式

(2)这时就需要一条语句,去联系yield语句和yield表达式

v = next(r)
print(v) v = r.send(None)  
print(v,)
---------------------
33 t1
1
由上面的两个结果可以看出send(None)执行的语句是
yield val
print("t1")
new = (yield val)
这两条描红语句,由上面的yield语句,执行到下面yield表达式中的返回值语句中,所以send(None)的返回值就是这里的yield返回的值

(3)下面的send方法执行一致

v = next(r)
print(v) v = r.send(None)
print(v,)
v = r.send("dsad")
print(v,) ---------------------- t1
1 由上面(2)的yield表达式开始(原来只是执行到返回值),现在开始赋值(send("dasd"))传递进去,然后执行到下一条yield返回值语句
t2
t3
dsad
------------------------
new = (yield val)  从这里赋值开始,到下面返回值结束
print("t2")
if new:
print("t3")
print(new)
new = (yield val)

(4)最后一步执行

v = next(r)
print(v) v = r.send(None)
print(v,)
v = r.send("dsad")
print(v,)
v = r.send("dsadds")
print(v,) -------------------------- t1 t2
t3
dsad dsadds
--------------------------
new = (yield val)
if new:
print(new)
这里语句中不在含有返回值,所以我们最后无法接收到值

当前面没有yield语句时,执行也是相似的

def rep(val):
# yield val
print("t1")      #先执行print('t1')和yield val
new = (yield val)   #然后执行赋值和向下执行到下一个yield 返回值语句
print("t2")
if new:
print("t3")
print(new)
new = (yield val)  #执行赋值,并且将下面语句执行完成*无返回值
if new:
print(new) -----------------------
r = rep(33)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)
v = r.send("dsadds")
print(v,3)    #由于无返回值,不会去执行
-----------------------
t1 t2
t3
dsad dsadds

可以看为:

若前面没有yield语句,则需要进行生成器挂起。(使用next或者send(None)),然后再使用send执行,方法相似

若有yield语句,就已经是生成器,我们只需要正常执行他(使用next或者send(None)),然后需要再次使用去激活send方法(连接yield语句和yield表达式中的返回值语句).....

案例:生成器实现文件流

#文件输入流
def FileInputStream(filename):
try:
f = open(filename,"r")
for line in f:
for byte in line:  #按字节获取数据
yield byte
except Exception as e:
print(repr(e)) #正常读取文件无错误
finally:
f.close()
return

#文件输出流
def FileOutputStream(inputStream,filename):
try:
f = open(filename,"w")
while True:
byte = next(inputStream) #若是在调用next方法时,迭代器没有值可以返回,就会引发一个StopIteration错误
f.write(byte)
except StopIteration as e:
print(repr(e)) #StopIteration()
f.close()
return FileOutputStream(FileInputStream('f'),"t2")

python---基础知识回顾(七)迭代器和生成器的更多相关文章

  1. python爬虫主要就是五个模块:爬虫启动入口模块,URL管理器存放已经爬虫的URL和待爬虫URL列表,html下载器,html解析器,html输出器 同时可以掌握到urllib2的使用、bs4(BeautifulSoup)页面解析器、re正则表达式、urlparse、python基础知识回顾(set集合操作)等相关内容。

    本次python爬虫百步百科,里面详细分析了爬虫的步骤,对每一步代码都有详细的注释说明,可通过本案例掌握python爬虫的特点: 1.爬虫调度入口(crawler_main.py) # coding: ...

  2. python基础(8)--迭代器、生成器、装饰器

    1.迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外,迭代器的一大优 ...

  3. Python 基础 内置函数 迭代器与生成器

    今天就来介绍一下内置函数和迭代器 .生成器相关的知识 一.内置函数:就是Python为我们提供的直接可以使用的函数. 简单介绍几个自己认为比较重要的 1.#1.eval函数:(可以把文件中每行中的数据 ...

  4. python基础15上_迭代器_生成器

    # 迭代器和生成器 # 迭代器: # 双下方法 : 很少直接调用的方法.一般情况下,是通过其他语法触发的 # 可迭代的 —— 可迭代协议 含有__iter__的方法('__iter__' in dir ...

  5. python基础知识14---迭代器、生成器、面向过程编程

    阅读目录 一 迭代器 二 生成器 三 面向过程编程 一 迭代器 1 迭代的概念 #迭代器即迭代的工具,那什么是迭代呢? #迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初 ...

  6. python基础知识回顾之列表

    在python 中,主要的常用数据类型有列表,元组,字典,集合,字符串.对于这些基础知识,应该要能够足够熟练掌握. 如何创建列表: # 创建一个空列表:定义一个变量,然后在等号右边放一个中括号,就创建 ...

  7. Py修行路 python基础 (十一)迭代器 与 生成器

    一.什么是迭代? 迭代通俗的讲就是一个遍历重复的过程. 维基百科中 迭代(Iteration) 的一个通用概念是:重复某个过程的行为,这个过程中的每次重复称为一次迭代.具体对应到Python编程中就是 ...

  8. python基础知识13-迭代器与生成器,导入模块

    异常处理作业讲解 file = open('/home/pyvip/aaa.txt','w+') try: my_dict = {'name':'adb'} file.write(my_dict['a ...

  9. python基础(八)-迭代器与生成器

    一.迭代器 li=[1,2,3] f=li.__iter__() print(f) print(f.__next__()) print(f.__next__()) print(f.__next__() ...

  10. python基础15下_迭代器_生成器

    print(dir([])) #告诉我列表拥有的所有方法 # 双下方法 # print([1].__add__([2])) print([1]+[2]) ret = set(dir([]))& ...

随机推荐

  1. UVA - 10635 Prince and Princess LCS转LIS

    题目链接: http://bak.vjudge.net/problem/UVA-10635 Prince and Princess Time Limit: 3000MS 题意 给你两个数组,求他们的最 ...

  2. java中方法传入参数时:值传递还是址传递?

    JAVA中的数据类型有两大类型: ① 基本数据类型:逻辑型(boolean).文本型(char).整数型(byte.short.int.long).浮点型(float.double) ② 引用数据类型 ...

  3. ADT图及图的实现及图的应用

    图: 图中涉及的定义: 有向图: 顶点之间的相关连接具有方向性: 无向图: 顶点之间相关连接没有方向性: 完全图: 若G是无向图,则顶点数n和边数e满足:0<=e<=n(n-1)/2,当e ...

  4. Qt之美(一):d指针/p指针详解(二进制兼容,不能改变它们的对象布局)

    Translated  by  mznewfacer   2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持 ...

  5. CSS 报错

  6. Python Matplotlib绘图库 安装

    一般我们在做科学计算的时候,首先会想到的是matlab,但是呢,一想到matlab安装包那么大,我就有点不想说什么了. Matplotlib 是python最著名的绘图库,它提供了一整套和matlab ...

  7. ACM数论之旅13---容斥原理(一切都是命运石之门的选择(=゚ω゚)ノ)

    容斥原理我初中就听老师说过了,不知道你们有没有听过(/≧▽≦)/ 百度百科说: 在计数时,必须注意没有重复,没有遗漏. 为了使重叠部分不被重复计算,人们研究出一种新的计数方法. 这种方法的基本思想是: ...

  8. Anaconda多版本Python管理以及TensorFlow版本的选择安装

    Anaconda是一个集成python及包管理的软件,记得最早使用时在2014年,那时候网上还没有什么资料,需要同时使用py2和py3的时候,当时的做法是同时安装Anaconda2和Anaconda3 ...

  9. 深入理解JAVA虚拟机阅读笔记5——Java内存模型与线程

    Java内存模型是定义线程共享的变量的访问规则(实例字段.静态字段和构成数组对象的元素),但不包括线程私有的局部变量和方法参数. 1.主内存与工作内存 Java内存模型规定,所有的变量都必须存储在主内 ...

  10. HDU3829_Cat VS Dog

    题目是这样的,给定一些人喜欢某只猫或者狗,讨厌某只猫或者狗.求最多能够同时满足多少人的愿望? 题目很有意思.建模后就很简单了. 对于同一只猫或者狗,如果有一个讨厌,另一个人喜欢,那么这两个连一条边.最 ...