迭代器、可迭代对象、生成器详解
可迭代对象
我们现在是从结果分析原因,能被for循环的就是“可迭代的”,但是如果正着想,for怎么知道谁是可迭代的呢?
假如我们自己写了一个数据类型,希望这个数据类型里的东西也可以使用for被一个一个的取出来,那我们就必须满足for的要求。这个要求就叫做“协议”。
可以被迭代要满足的要求就叫做可迭代协议。可迭代协议的定义非常简单,就是内部实现了__iter__()
方法。
如果这个对象中有_ iter _()
方法,这个对象就是可迭代对象
if '__iter__' in dir(str)
通俗易懂 :可以被for循环迭代的对象就是可迭代对象。
isinstance()判断一个对象是否是Iterable对象:
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
迭代器
迭代器是什么
迭代器只不过是一个实现迭代器协议的容器对象。它基于两个方法:
next 返回容器的下一个项目
_ iter _ 返回迭代器本身
i = iter('asd')
next(i)
>>>'a'
next(i)
>>>'s'
next(i)
>>>'d'
next(i)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
当序列便利完时,将抛出一个StopIteration异常。这将使迭代器与循环兼容,因为它们将捕获这个异常以停止循环。要创建定制的迭代器,可以编写一个具有next
方法的类
为什么使用迭代器
“流式”数据处理方式*少内存消耗
例如: 当处理文件时,把所有数据全部取出来放到内存里面进行处理会导致程序消耗大量内存
一般我们会一部分一部分的对文件内容进行处理:
for text_line in open("xx.txt"):
print text_line
open("xx.txt")
返回的是可迭代的对象,所以,可以渐进式地对文件的内容进行处理,即按行来读取文件,并进行处理,
而不是,直接把全部文件一下加载到内存中。
支持方便用for语句对数据进行处理
python内置的一些常见的像类型像数组、列表甚至字符串等都是可迭代类型,
这样我们就能方便for语句这个语法方便对数据进行消费,不需要自己记录索引位置,人肉循环:
for i in [1,2,3,4]:
print(i)
迭代器的优点
很方便使用,且只能取所有的数据取一次
节省内存空间
迭代器的内部
可迭代对象:
对象里面包含__iter()__
方法的实现,对象的iter
函数经调用之后会返回一个迭代器,里面包含具体数据获取的实现
迭代器:
包含有next
方法的实现,在正确范围内返回期待的数据以及超出范围后能够抛出StopIteration
的错误停止迭代。
迭代器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
生成器都是Iterator
对象,但list、dict、str
虽然是Iterable
,却不是Iterator
把list、dict、str
等Iterable
变成Iterator
可以使用iter()
函数:
迭代协议
必须拥有_iter_
方法和_next_
方法。
迭代器和可迭代对象
正式的说法:
一个实现了iter
方法的对象是可迭代的,一个实现next
方法的对象则是迭代器
个人理解:
仅实现了iter
方法的是可迭代对象,可迭代对象可以通过._ iter _()
方法转成迭代器。
迭代器有next
和_ iter _
方法 只要存在这两个方法的都是迭代器。迭代器是一个存储的容器,每当调用next
方法的时候才会将数据拿出。并且只执行一次
即迭代器可以作为一个数据生成器
生成器
生成器的本质就是迭代器
迭代器 我们知道是用来迭代可迭代对象的,而生成器是用来迭代方法的
我们知道的迭代器有两种:一种是调用方法直接返回的,一种是可迭代对象通过执行iter方法得到的,迭代器有的好处是可以节省内存。
如果在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器。
生成器Generator:
本质:迭代器(所以自带了iter方法和next方法,不需要我们去实现)
特点:惰性运算,开发者自定义
1.生成器函数:
常规函数定义,但是,使用yield
语句而不是return
语句返回结果。yield
语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
只要有 yield
的关键字的函数就是生成器函数
一个包含yield
关键字的函数就是一个生成器函数。yield
可以为我们从函数中返回值,但是yield
又不同于return
,return
的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。
- 特点:
调用函数的之后函数不执行,返回一个生成器
每次调用next方法的时候会取到一个值
直到取完最后一个,在执行next会报错
从生成器中取值的几个方法
1.next
2.for
3.数据类型的强制转换 : 占用内存
例1:
监听文件
import time
def tail(filename):
f = open(filename)
while True:
line = f.readline() # 读取文件中新的文本行
if not line:
time.sleep(0.1)
break
yield line
调用
tail_g = tail('tmp')
for line in tail_g:
print(line)
例2
预激协程的装饰器
计算移动平均值
def init(func):
def inner(*args,**kwargs):
g = func(*args,**kwargs)
next(g)
return g
return inner
@init
def averager():
total = 0.0
count = 0
average = None
while 1:
trem = yield average
total += term
count += 1
average = total / count
调用
g_avg = averager()
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
理解
我们都知道 生成器函数只有当调用next方法时,才会进入函数内部进行执行,为了方便调用 将next放到装饰器中,这样使调用更方便、易懂
当然我们就上述代码也可以不写装饰器 而写成
g_avg = averager()
next(g_avg)
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
但是试想,如果有N多生成器函数,我们需要N多next()方法,语句看起来也会混乱
yield from
def gen1():
for c in 'AB':
yield c
for i in range(3):
yield i
print(list(gen1()))
def gen2():
yield from 'AB'
yield from range(3)
print(list(gen2()))
2.生成器表达式:
类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
语法:
(x for x in range(10))
这和列表生成式很类似,区别在于一个是()
一个是[]
,语法是一样的
列表生成式
生成的结果是一个列表
语法:
[x for x in range(10)]
从数组中依次将内容取出
相当于
x = []
for i in range(10):
x.append(i)
[x * 2 for x in range(10) if x % 2 == 0]
从数组中 将 符合条件 的内容取出, 并计算
相当于
x = []
for i in range(10):
if i % 2 ==0:
x.append(i * 2)
[x * y for x in range(10) for y in range(10)]
循环嵌套
相当于
x = []
for x in range(10):
for y in range(10):
x.append(x * y)
[x % 2 == 0 for x in range(10)]
for x in range(10):
if x % 2 == 0:
return True
else:
return False