生成器和迭代器

迭代器

迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件

1 特点

  1. 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
  2. 不能随机访问集合中的某个值 ,只能从头到尾依次访问
  3. 访问到一半时不能往回退
  4. 便于循环比较大的数据集合,节省内存

2 迭代器的概念

迭代器协议:内部含有__next____iter__方法的就是迭代器

可迭代对象:内部有__iter__方法就是可迭代对象。还有__next__的话就是迭代器

迭代器协议和可迭代协议:

  1. 可以被for循环的都是可迭代的
  2. 可迭代的内部都有__iter__方法
  3. 只要是迭代器,一定是可迭代的
  4. 可迭代的.__iter__()就可以得到一个迭代器
  5. 迭代器的.__next__方法可以一个一个的获取值
l = [1,2,3]  # l 可迭代对象
print('__iter__' in dir(l)) # True
print('__next__' in dir(l)) # False 
l = iter(l) # 可迭代对象通过调用iter()方法就能得到一个迭代器
print(l) # <list_iterator object at 0x0579DD30>
print('__iter__' in dir(l)) # True
print('__next__' in dir(l)) # True

for循环其实就是在使用迭代器。每循环一次,调用一次__next__方法

只有是可迭代对象才能使用for循环。当我们遇到一个新的变量,如果无法确定是否可以使用for,可以查看这个变量是否有__iter__方法,有就是可迭代的,可以使用for。

Iterator,可迭代对象,直接给出对象对应的内存地址。

3 迭代器的好处

  1. 从容器类型中取值,会把所有值都取到。
  2. 节省内存空间。 迭代器并不会在内存中再占用一大块内存,而是随着循环,每次生成一个(每次next就给一个)

生成器

一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator);如果函数中包含yield语法,那这个函数就会变成生成器;

生成器本质就是 迭代器。

def func():
    yield 1
    yield 2    
    yield 3    
    yield 4

上述代码中:func函数称为生成器,当执行此函数func()时会得到一个迭代器。

>>> temp = func()
>>> temp.__next__()1
>>> temp.__next__()2
>>> temp.__next__()3
>>> temp.__next__()4
>>> temp.__next__()Traceback (most recent call last):  File "<stdin>", line 1, in <module>StopIteration
从生成器中取值的几个方法:
	next(生成器)
	for循环
	数据类型的强制转换 : 占用内存

1 生成器的表现形式

1 生成器函数

本质上就是我们自己写的函数。只要含有yield关键词的函数都是生成器函数。

# yield不能和return共用且需要写在函数内
l = [1,2,3,4,5]

def generator():
    print(1)
    yield 'a'
    
ret = generator() # ret 是执行之后得到的生成器
print(ret) # 打印的是生成器对象 <generator object generator at 0x04FCABB0>
print(ret.__next__())# 可以使用__next__方法
print(dir(ret))
#['__class__', '__del__', '__delattr__', '__dir__', '__doc__',
 '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
 '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__',
 '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__',
 '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
 '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running',
 'gi_yieldfrom', 'send', 'throw']

执行之后会得到一个生成器作为返回值

# 不同的生成器之间互不影响。
def wahaha():
    for i in range(2000000):
        yield '娃哈哈%s'%i
g = wahaha()
g1 = wahaha()
print(g.__next__()) # 0
print(g1.__next__())# 0

特点

  1. 调用函数的之后函数不执行,返回一个生成器。

  2. 每次调用next方法的时候会取到一个值。

  3. 直到取完最后一个,在执行next会报错 StopIteration

2 生成器表达式

g = (i for i in range(10)) # 注意:括号必须是()才返回生成器,括号不一样返回不同的值。几乎不占用内存
print(g)
for i in  g:
    print(i)

2 监听文件输入的案例

# 内容中有检索的字符,就输出对应行
def tail(filename):
    f = open(filename,encoding='utf-8')
    while True:
        line = f.readline()
        if line.strip():
            yield line.strip()

g = tail('file')
for i in g:
    if 'python' in i:
        print('***',i)

3 案例

# 生成器实现:有一个文件,从文件里分段读取内容,在读出来的内容前面加上一个'***',再返回给调用者
def readfile(filename):
    f = open(filename,encoding='utf8')
    while True:
        content = f.readline()
        if content:
            yield '***'+content

g = readfile('file')
for i in  g:
    print(i)

生成器函数进阶

send 获取下一个值的效果和next基本一致。只是在获取下一个值的时候,会给上个yield的位置传递一个数据

1 使用send的注意事项

​ 第一次使用生成器的时候 是用next获取下一个值,最后一个yield不能接受外部的值

def generator():
    print(123)
    content = yield 1 
    print('=======',content)
    print(456)
    arg = yield 2
    ''''''
    yield
# g1 = generator()
# g1.__next__()
# print('***',generator().__next__())

g = generator() #得到生成器
ret = g.__next__() # 打印123,执行第一个yield
print('***',ret) # *** 1
ret = g.send('hello')   #send的效果和next一样,只是在获取下一个值的时候,会给上个yield的位置传递一个数据,此时 content = 'hello',继续向后运行,打印content,456
print('***',ret) # *** 2

2 获取平均移动值的案例

# 获取移动平均值
# 10 20 30 10
# 10 15 20 17.5
# avg = sum/count
def average():
    sum = 0
    count = 0
    avg = 0
    while True:
        num = yield avg
        sum += num    # 10
        count += 1    # 1
        avg = sum/count

avg_g = average() # 得到生成器
avg_g.__next__() # 此时执行第一个next方法,返回avg的值0
avg1 = avg_g.send(10)# 传递一个10给num,继续往后执行,此时num=10,sum=0+10,count=0+1,avg=10,循环继续,执行下一个yield。
avg1 = avg_g.send(20)# 再次传递一个20给num,继续往后执行,此时num=20,sum=10+20=30,count=1+1=2,avg=15,循环继续,执行下一个yield
print(avg1) # 此时avg1=15

3 预激生成器的装饰器 案例

def init(func):   #装饰器
    def inner(*args,**kwargs):
        g = func(*args,**kwargs)    #g = average()
        g.__next__() #将第一次调用的next方法,放到了装饰器中
        return g
    return inner

@init
def average():
    sum = 0
    count = 0
    avg = 0
    while True:
        num = yield avg
        sum += num    # 10
        count += 1    # 1
        avg = sum/count

avg_g = average()   #===> inner
ret = avg_g.send(10)
print(ret)
ret = avg_g.send(20)
print(ret)

小案例

yield from 等同于 for ...in...

def generator():
    a = 'abcde'
    b = '12345'
    for i in a:
        yield i
    for i in b:
        yield i
        
g = generator()
for i in g:
    print(i)
# 结果:
a
b
c
d
e
1
2
3
4
5
def generator():
    a = 'abcde'
    b = '12345'
    yield from a
    yield from b

g = generator()
for i in g:
    print(i)
# 结果:
a
b
c
d
e
1
2
3
4
5
原文地址:https://www.cnblogs.com/chenych/p/10939676.html