6-迭代器+生成器

迭代器

1. 可迭代对象

from collections import Interable 
print(isinstance([11,22,33],Interable))

如果要使一个对象是可迭代的,必须重写__iter__方法,这样才能通过Interable的检验,但是此时仍不能通过for函数遍历,所以仅仅是可迭代对象,而不是迭代器。

示例代码:

from collections.abc import Iterable

class ClassMate():
    def __init__(self) -> None:
        self.name = list()
    
    def add(self,name):
        self.name.append(name)
    
    def __iter__(self):
        pass

classmate = ClassMate()
print("判断ClassMate是否是可迭代对象:",isinstance(classmate,Iterable))

2. 迭代器

迭代器必须重写__iter__方法和__next__方法,其中,__iter__方法必须返回一个对象的引用。

可以使用for循环遍历迭代器,for temp in xxx_obj的执行过程:

  1. 判断对象xxx_obj是否有__iter__方法(可迭代对象)
  2. 在第一步成立的条件下,调用iter函数,得到对象xxx_obj__next__的返回值,其返回值就是一个迭代器
  3. 自动调用迭代器的next函数,即调用迭代器的__next__方法,直到抛出StopIteration异常。

对于这样一个for循环

for i in seq:
    do_something_to(i)

实际上是这样工作的

fetch_iterator = iter(q)
while True:
    try:
        i = fetch_iterator.next()
    except StopIteration:
        break
    do_something_to(i)

迭代器非常重要,python3中字典、文件、列表都是可迭代的对象。

3. 动手写一个可迭代的类

from collections.abc import Iterable
from collections.abc import Iterator
# 实现将一个可迭代的类

class ClassMate(object):
    def __init__(self) -> None:
        self.names = list()

    def add(self,name):
        self.names.append(name)

    def __iter__(self):
        # 返回一个迭代器的实例对象
        return ClassIterator(self)

class ClassIterator(object):
    def __init__(self,obj):
        self.obj = obj
        # 通过实例属性记录前一次指针的位置
        self.current_num = 0

    def __iter__(self):
        pass
    def __next__(self):
        """next方法执行此方法"""
        if self.current_num < len(self.obj.names):
            # 手动防止越界
            ret =  self.obj.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            raise StopIteration
            # for 循环会自动捕获错误

    
classmate = ClassMate()
classmate.add('小王')
classmate.add('小美')
classmate.add('小明')

class_iterator = iter(classmate)


print("判断classmate是否是可迭代对象",isinstance(classmate,Iterable))
print("判断class_iterator是否是迭代器",isinstance(class_iterator,Iterator))

for name in classmate:
    print(name)

# Output:
# 判断classmate是否是可迭代对象 True
# 判断class_iterator是否是迭代器 True
# 小王
# 小美
# 小明

执行过程:
在这里插入图片描述

注意:

  1. 可迭代对象A的__iter__返回一个迭代器的 实例对象B, 迭代器B初始化时需要将 可迭代对象A 传入 初始化方法;
  2. 迭代器实例对象通过一个 实例属性self.current_num记录当前读取的位置
  3. 需要手动防止越界,否则for语句会不断执行__next__,并捕获返回的None,此时,可通过抛出 StopIteration 错误,for循环会自动捕获。

4. 封装到一个类中

上面的方法使用了两个类,有点浪费资源,我们将其全部封装到一个类中

from collections.abc import Iterable
from collections.abc import Iterator
# 实现将一个可迭代的类

class ClassMate(object):
    def __init__(self):
        self.names = list()
        self.current_count = 0

    def add(self,name):
        self.names.append(name)

    def __iter__(self):
        # 返回自身实例对象
        return self
    
    def __next__(self):
        if self.current_count<len(self.names):
            ret = self.names[self.current_count]
            self.current_count+=1
            return ret
        else:
            raise StopIteration
    
classmate = ClassMate()
classmate.add('小王')
classmate.add('小美')
classmate.add('小明')

class_iterator = iter(classmate)


print("判断classmate是否是可迭代对象",isinstance(classmate,Iterable))
print("判断class_iterator是否是迭代器",isinstance(class_iterator,Iterator))

for name in classmate:
    print(name)

# Output:
# 判断classmate是否是可迭代对象 True
# 判断class_iterator是否是迭代器 True
# 小王
# 小美
# 小明

5. 迭代器的应用

range和xrange

【补充】rangexrange的区别:

range和xrange的区别:在python2中range返回数值,xrange返回一个可迭代的对象,占用了极小的空间,

在python3中,range也返回的是一个可迭代对象了。

使用迭代器生成斐波那契数列

例如可以使用迭代器实现斐波那契数列

# 一般实现
a = 0
b = 1
i = 0
while i<10:
    a,b = b,a+b
    print(a)
# 迭代器实现


class  Fibonacci(object):

    def __init__(self,all_num):
        self.max_num = all_num
        self.count_num = 0
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.count_num < self.max_num:
            self.count_num += 1
            self.a,self.b = self.b,self.a+self.b
            return self.a
        else:
            raise StopIteration

fibo = Fibonacci(10)
for num in fibo:
    print(num)

列表解析式

列表解析,List comprehensinos,来自于Haskell语言,可以取代lambda、map和filter函数的部分使用场景。

语法[expr for iter_val in iterable if cond_expr]

例1:计算一个序列成员的平方

map(lambda x:x**2,range(6))
# 或者
[x**2 for x in range(6)]

例2:挑选出序列seq中的奇数

filter(lambda x: x%2,seq)
# or
[x for x in seq if x%2]

例3:计算文本中的单词个数【列表解析式支持多重循环和多个if语句,执行顺序从左到右】

f = open('test.txt','r')
len([word for line in f for word in line.split()])

生成器表达式和列表解析式很类似:(expr for iter_val in iterable if cond_expr),因为生成器表达式使用了“延迟计算”(lazy evaluation),所以在内存上比列表解析式更加有效。

生成器

生成器是一种特殊的迭代器,你只能对它迭代一次,因为它在运行时生成值,并不存储进内存。

生成器的函数实现

生成器由两种定义方式,第一种即是将列表解析式的[]换成(),不过更常用的是函数的形式:

例子:使用生成器生成斐波那契数列

def create_num(all_num):
    a,b = 0,1
    count_num = 0
    while count_num < all_num:
        yield a  
        a,b = b,a+b
        count_num += 1 
obj = create_num(10)
for i in obj:
    print(i)
    # 当执行到yield的时候,生成器会暂停输出,并返回结果

可见,生成器和函数的一个重要区别就是函数名()h会立即执行,而obj = 生成器()只是创建一个生成器对象,通过next obj的方式,返回内部yield对应的值,下次调用next的时候,则会从上次yield暂停的地方开始执行。

当然也可以使用while True的方式遍历生成器对象,但是 需要手动捕获异常 作为结束条件。

def create_num(all_num):
    a,b = 0,1
    count_num = 0
    while count_num < all_num:
        yield a  
        a,b = b,a+b
        count_num += 1 

obj = create_num(10)
while True:
    try:
        ret = next(obj)
        print(ret)
    except Exception as ret:
        print(ret.value)
        break

也可以创建多个生成器,互不影响,类似于类创建的对象之间的关系。

通过send方法从外向内给生成器传值

生成器的yield不仅能够将生成器内部的值传出来,还能够接收外部传送的数据,需要send方法传递。

demo:

def create_num(all_num):
    a,b = 0,1
    count_num = 0
    while count_num < all_num:
        ret_in = yield a  
        print(">>>ret>>>",ret_in)
        a,b = b,a+b
        count_num += 1 

obj = create_num(10)
ret_out = next(obj)
print(ret_out)

ret_out = obj.send("hahahah")
print(ret_out)

首先,执行yield a,将a的值传递给ret_out,然后在send调用时,继续执行这一行,但是yield已经没有返回值,这里传递给ret_in的值,就是send中的参数。

注意:send不能在一开始处就调用,否则会报错。

小结:

拥有一个__iter____next__方法的类的实例对象叫做迭代器,可以节省内存空间;拥有yield的函数叫生成器,可以在yield处暂停执行,并在下次调用时从上次暂停的地方重新开始,两者都可以通过next进行遍历。

猜你喜欢

转载自blog.csdn.net/weixin_43721070/article/details/121774749
今日推荐