一、迭代器

迭代器的特性:

    迭代是Python中最强有力的特性之一,可以把迭代看成是一种处理序列中元素的方式。

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

    可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)。
    
    所有的Iterable均可以通过内置函数iter()来转变为Iterator。

    出可迭代对象的个数会报错StopIteration,是一个结束信号

迭代器的优点:
    
    1、迭代器提供一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的对象了(字典、集合、文件)
    2、迭代器和列表比较,迭代器是惰性计算,更节约内存

迭代器的缺点:
    1、永远不能获取迭代器的长度,使用不如列表索引取值灵活
    2、一次性的,只能往后取值,不能往前,不能像索引一样取得指定位置的值

二、查看可迭代和迭代器的方法

from collections import Iterable,Iterator
        s='hello'
        l=[1,2,3]
        t=(1,2,3)
        d={'a':1}
        set1={1,2,3,4}
        f=open('a.txt')

        #都是可迭代,只要对象有__iter__()方法,都是可迭代的
        s.__iter__()
        l.__iter__()
        t.__iter__()
        d.__iter__()
        set1.__iter__()
        f.__iter__()
               
        #查看是否可以迭代的
        print(isinstance(s,Iterable))

        #查看是否是迭代器,只有文件是迭代器
        f.__next__()

        print(isinstance(f,Iterator))

三、手动访问迭代器中的元素

1、问题

我们要处理某个可迭代对象中的元素,但是基于某种原因不能也不想使用for循环。

2、解决方案

要手动访问可迭代对象中的元素,可以使用next()函数,然后自己编写代码来捕获stopiteration异常。例如:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
with open('passwd','r',encoding='utf8') as f:
    while True:
        try:
            line=next(f)
            print(line,end='')
        except StopIteration:
            break
#一般来说,stopiteration异常是用来通知我们迭代结束的,但是,如果是手动执行next(),也可以命令他返回一个值,比如None,示例如下:


with open('passwd','r',encoding='utf8') as f:
    while True:
        line=next(f,None)
        if line is None:
            break
        print(line,end='')
大多数情况下,我们会用for循环来访问可迭代对象中的元素,但是,偶尔也会碰到需要对底层迭代机制做更精细情况的控制。因此,了解迭代实际发生了些什么是很有必要的。for 循环也是先使用iter方法把要循环的对象生成一个迭代器,然后去遍历这个迭代器,遍历迭代器里的每个元素
>>> items=[1,2,3]
>>> i=iter(items)     #===> i.__iter__()
>>> next(i)           #===> i.__next__()
1
>>> next(i)
2
>>> next(i)
3
>>> next(i)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

四、生成器

生成器是一个包含yield关键字的函数,当它被调用时,在函数中的代码不会执行,而会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句,yield语句一位着会生成一个值,return语句一位着生成器要停止执行(不在生成任何东西,return语句只有在一个生成器中使用时才能进行无参数调用)换句话说,生成器是由两部分组成:生成器的函数和生成器的迭代器,生成器的函数使用def定义,包括yield部分,生成器的迭代器是这个函数返回的部分。
def countdown(n):
    print('starting to count from',n)
    while n>0:
        yield n
        n-=1
    print('Done!')
g=countdown(5)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))

#执行结果如下
starting to count from 5
5
4
3
2
1

#函数中出现了yield语句,就会把这个函数转换成生成器。与普通的函数不同,生成器只会在响应迭代操作是才会运行。调用时需要使用next方法才能执行,没执行一次,当遇到下一个yield时就会停止,下次执行next时,将从当前yield往下执行,知道遇到一个yield
生成器的本质就是一个迭代器,同时也是一个函数,调用的方式和函数类似,yield也相当于函数中的return
    yield和return的区别:return只能返回一次函数就会彻底结束,而yield能返回多次值
yield到底干了什么事情:
    1、yield把函数变成生成器(迭代器),把iter和next方法封装在函数内部
    2、函数在暂停一及继续下一次运行时的状态是yield保存

五、协程

from urllib.request import urlopen
def init(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        next(res)
        return res
    return wrapper

@init
def get():
    while True:
        url=yield
        res=urlopen(url).read()
        print(res)

g=get()  #g是一个迭代器
# next(g) #这里使用一个init的装饰器,自动做了next的工作
g.send("http://www.baidu.com")     #使用send把括号内的URL传递给yield
g.send('http://www.python.org')
g.send('http://www.163.com')
协程函数,是把yield改为表达式的方式,改过send代替next给yield传值
e.send于next(e)的区别
    1、如果函数内yield是表达式形式,那么必须先next(e),让生成器在第一个yield位置处等着
    2、二者的共同之处都可以让函数在上次暂停的位置继续运行,不一样的地方在于,send在触发一下次代码的执行时,给yield传值