python技巧

迭代器和生成器

2018-09-02  本文已影响0人  陆_志东

首先说说python容器:

容器是一种把Python对象存放在一起的数据结构,对于容器来说,我们可以逐个的迭代获取里面的元素,也可以使用in or not in 判断容器中是否有我们想要的对象.在Python中常见的容器对象有:

list1 = [1,2,3]
print(4 in list1)
>>False
print(1 in list1)
>>True
for obj in list1:
    print(obj)
>>1
>>2
>>3

如何判断一个对象是否可迭代?

使用isinstance判断对象是不是Iterable类的实例
from collections import Iterable
print(isinstance([],Iterable))
>>True
print(isinstance(1,Iterable))
>>False

如何判断一个对象是否是迭代器?

from collections import Iterator
print(isinstance([], Iterator))
>>False
print(isinstance(iter([]), Iterator))
>>True
print(isinstance(iter("abc"), Iterator))
>>True

我们如何实现一个可迭代对象呢?

我们需要知道在Python中万物皆对象,想要一个对象是可迭代对象,只需要在对象的创建类里面添加__iter__魔法方法即可,__iter__魔法方法作用就是让一个对象可迭代
比如我们先新建一个不可迭代的类MyIter

class MyIter:
    def __init__(self):
        pass
myiter1 = MyIter()  # 实例化出来的对象是不可迭代的,对不可迭代的对象遍历,会引发异常
for data in myiter1:
    print(data)
>>TypeError: 'MyIter' object is not iterable

下面通过__iter__魔法方法让我们的不可迭代类MyIter变为可迭代的类

class MyIter:
    def __init__(self):
        pass

    def __iter__(self):
        return # 这里我们直接return,先什么都不做

myiter1 = MyIter()
for data in myiter1:  # 一样会触发异常,这是因为我们并没有在__iter__里面返回一个迭代器或者生成器
    print(data)
>>TypeError: iter() returned non-iterator of type 'NoneType'
# 不过没关系,他已经是一个可迭代对象了,请看
from collections import Iterable
print(isinstance(myiter1,Iterable))
>>True   # 结果已经显示为可迭代了

接下来我们解决for循环TypeError的问题,在iter魔法方法里面返回一个迭代器或者一个生成器,生成器会在迭代器下面进行讲解,这里我们先返回一个迭代器

class MyIter:
    def __init__(self):
        pass

    def __iter__(self):
        return iter([1,2,3])  # 注意是迭代器,不是可迭代对象

myiter1 = MyIter()
from collections import Iterable
print(isinstance(myiter1,Iterable))
>>True
for data in myiter1:
    print(data)
>>1
>>2
>>3
注意:迭代器和生成器都是一次性的,不能回滚,如果想要再次使用,需要重新实例化
下面再次使用myiter1,是可以继续得到1,2,3的
这是因为我们的写法原因,我们这里的写法每次都会重新return一个迭代器
for data in myiter1:
    print(data)
>>1
>>2
>>3

下面修改我们的写法,来演示一次性效果
class MyIter:
    def __init__(self):
        self.can_iter = iter([1,2,3])

    def __iter__(self):
        return self.can_iter

myiter1 = MyIter()
for data in myiter1:
    print(data)
print("--------------")
for data in myiter1:
    print(data)
输出结果如下
>>1
>>2
>>3
>>--------------
可以看到第二个for循环什么都没有输出,如果我们想要再次使用就需要再次实例化
myiter2 = MyIter()
for data in myiter2:
    print(data)
>>1
>>2
>>3

可以看到上面的代码并没有继续出现TypeError的问题,所以我们可以看出__iter__魔法方法的实际作用就是为了返回一个迭代器或者生成器的.
对于迭代器和生成器我们也可以使用next函数去获取迭代器or生成器里面的内容,不过当内容获取完之后会触发StopIteration异常

iter1 = iter([0,1,2])
print(next(iter1))
print(next(iter1))
print(next(iter1))
print(next(iter1))
>>0
>>1
>>2
Traceback (most recent call last):
  StopIteration
但是需要注意的是如果使用for循环并不会看到StopIteration这个异常
for data in iter1:
    print(data)
程序执行后什么都不会输出,且看不到StopIteration异常.
什么都不输出是因为迭代器是一次性的,不能回退,所以没有内容可输出
而为什么没有StopIteration异常,这是因为for循环会捕获这个异常,当for循环捕捉到StopIteration,就会停止迭代

由上面的代码我们可以总结出for...in 循环的本质

for 循环对可迭代对象进行迭代的过程:
1.首先调用__iter__魔法方法,获取到迭代器或生成器
2.使用next函数获取迭代器和生成器的下一个值
3.当没有值可获取之后,触发StopIteration异常反馈给for循环
4.for循环捕获StopIteration异常,并停止循环

其实问题说到这就会发现上面__iter__方法返回的迭代器,是用iter函数创建出来的,我们能不能自己定义一个迭代器供iter方法返回呢?答案是可以,请继续看这篇文章

如何自定义一个迭代器

自定义一个迭代器需要类的构造中要有__next__方法.这个方法的作用就是能够记录我们已经迭代到的位置,当没有元素可以迭代之后触发StopIteration异常.
下面我们的做法是给自己的可迭代类添加一个__next__方法,变成一个迭代器,同时通过__iter__方法返回我们实例本身

就以生成斐波那契数列为例:
import sys


class MyIter:
    def __init__(self, max_size=None):
        self.prev_num = 0
        self.current_num = 1
        self.index = 0
        self.max_size = max_size
        if max_size is None:
            self.max_size = sys.maxsize

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= self.max_size:
            raise StopIteration
        self.index += 1
        return_num =self.prev_num
        self.prev_num = self.current_num
        self.current_num += return_num
        return return_num


myiter1 = MyIter()  # 实例化一个不限制大小的迭代器
for i in range(10):
    print(next(myiter1), end=" ")
print()
>>0 1 1 2 3 5 8 13 21 34 
myiter2 = MyIter(10)  # 限制大小
for j in myiter2:
    print(j, end=" ")
print()
>>0 1 1 2 3 5 8 13 21 34 

由上面的例子得出我们可以使用__next__方法创造出各种各样算法的迭代器

使用迭代器的好处

如果我们需要一批数据,又知道数据产生的方法,那么我们就可以使用迭代器来生产数据,而不是事先生成放在内存中
注意在Python中并不是只有for循环可以迭代,listtuple也可以迭代
比如你使用高级函数mapfilter,返回的是一个迭代器,直接print是看不到内容的,那么有两个选择,for循环遍历,使用list迭代转换为列表打印这个列表

生成器

生成器其实就是特殊的迭代器

创建生成器的方法一:改造列表生成式

list1 = [x for x in range(5)]
print(list1)
>>[0, 1, 2, 3, 4]
generate1 = (x for x in range(5))
print(generate1)
>><generator object <genexpr> at 0x0000000001EF5938>
print(list(generate1))  # list迭代转换
>>[0, 1, 2, 3, 4]
generate2 = (x for x in range(5))
print(next(generate2))
>>0
print(next(generate2))
>>1
print(next(generate2))
>>2
print(next(generate2))
>>3
print(next(generate2))
>>4
print(next(generate2))
Traceback (most recent call last):
  StopIteration

方式二:改造函数(yield)

同样以斐波那契数列为例
import sys


def generate_feibo(max_size=None):
    if max_size is None:
        max_size = sys.maxsize
    prev_num = 0
    current_num = 1
    index = 0
    while index < max_size:
        return_num =prev_num
        yield return_num
        index += 1  # 这一步放yield前面和后面执行效果都一样
        prev_num = current_num
        current_num += return_num
    return


myiter1 = generate_feibo()  # 实例化一个不限制大小的生成器
for i in range(3):
    print(next(myiter1), end=" ")
>>0 1 1 
print()
myiter2 = generate_feibo(3)
for data in myiter2:
    print(data, end=" ")
>>0 1 1 

使用yield之后,就不需要手动抛出StopIteration异常了,当生成器没有使用yield返回数据,而是使用return结束函数的时候会自动抛出StopIteration异常

使用生成器的实例二:读取一个超大文件

def read_file():
    with open("./oid.txt","r",encoding="utf-8",newline="\n") as f:
        while True:
            data = f.readline().strip()
            if data is None or not data:
                break
            yield data
    return
上一篇下一篇

猜你喜欢

热点阅读