python 迭代器与生成器

迭代器

迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,迭代器只能往前不会后退。

可迭代对象

我们已经知道可以对list、tuple、str等类型的数据使用 for...in... 的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。

    

 

判断一个东西是否可以迭代,导入 Iterable,当是Iterable的子类时,则可以迭代。使用isinstance()方法

 

那类对象是否可以迭代,创建一个 demo 测试一下

和数字类型一样运行报错   'Classmate' object is not iterable         

 

 

那如果想让一个类对象可以被迭代呢?我们只要在类里实现 __iter__方法即可

此时程序依然报错,但是报错内容为,iter() 方法返回 None 

 

我们使用 Iterable 验证一下,结果很明显,说明实现了 iter方法的类对象是 可迭代的对象

           

         

 

此时我们解决 Iter() 方法 返回值报错问题:

当使用for 循环迭代一个类对象时,

1.首先判断该对象是否可以被迭代(即,对象的类中是否实现了__Iter__方法

2.继续判断 __Iter__方法是否返回一个迭代器(返回一个类对象,实现该对象的类中必须有__Iter__和 __next__ 方法 )

3.然后调用iter函数(不是__iter__方法),得到迭代器(对象),继续调用next函数,此函数会调用迭代器的__next__方法,

   __next__方法return的值,就是     for temp in 对象 的 temp值 ,每for循环一次,调用一次__next__方法

 

使用 Iterator 验证 classmate_iterator 对象,返回True,说明 对象的一个迭代器

                    运行结果:

 

使用next方法迭代对应的迭代器对象,即返回迭代器 __next__方法的返回值

                  运行结果:

 

上述验证使用的init()方法和next()方法都会被for循环调用,此时我们为了将可迭代对象的对应属性传递到迭代器,

在__init__方法中传递self

                运行结果:

 

最后我们优化上述代码,将可迭代对象和迭代器放在同一个类中,即同时实现__iter__和__next__方法

#!/usr/bin/env python
# _*_ coding:utf-8 _*_


class Classmate(object):
    def __init__(self):
        self.names = list()
        self.current_num = 0  # 计数器

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

    def __iter__(self):
        """可迭代的对象,即可以使用 for,必须实现 Iter() 方法"""
        return self

    def __next__(self):
        if self.current_num < len(self.names):  # 防止越界
            ret = self.names[self.current_num]
            self.current_num += 1
            return ret
        # 当if条件不成立时,默认继续返回None值,使用StopIteration 结束迭代
        else:
            raise StopIteration


classmate = Classmate()
classmate.add("老王")
classmate.add("王二")
classmate.add("张三")

for name in classmate:
    print(name)

 

如果一个对象是迭代器,它一定是可迭代,对应的,如果一个对象是可迭代的,那它不一定是迭代器。

 

迭代器的作用:

下面两种代码均可实现得到前10位的斐波那契数列

而更推荐使用第二种迭代器,因为代码占用内存更小,保存的是生成的方式,需要时在使用,而不是保存生成的结果

nums = list()

a = 0 
b = 1
i = 0
while i < 10:
    nums.append(a)
    a, b = b, a+b
    i += 1


for num in nums:
    print(num)
class Fibonacci(object):
    def __init__(self, all_num):
        self.all_num = all_num
        self.current_num = 0
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_num < self.all_num:
            ret = self.a
        
            self.a, self.b = self.b, self.a+self.b
            self.current_num += 1

            return ret
        else:
            raise StopIteration


fibo = Fibonacci(10)


for num in fibo:
    print(num)

 

我们进行数据类型转换时,也是利用迭代器的过程

生成一个空列表,然后进行迭代打印输出

 

 

生成器

特殊的迭代器(不需要iter next方法)

创建生成器

使用列表推导式生成一个列表,如下图

将中括号换成小括号,nums为一个对象,这就是生成器

此时我们就可以迭代nums

两者比较,一种是返回数据,一种是返回生成数据的方式,即节省空间。

 

第二种创建生成器的方式(常用)

定义一个函数,当 yield 存在时, 函数则为 生成器

如下例生成前三位的斐波那契数列数列,函数中存在 yield,此时 函数为一个生成器的模板,调用函数的返回值 obj 是一个 生成器。

运行过程:

使用for循环迭代 obj ,循环时,从上往下依次执行,当执行到 yield 语句是,将 yield 后的 a 的值赋予 num,执行暂停,打印输出 num,结束第一次循环,第二次循环,直接从 yield语句下方执行,当再次碰到 yield语句时,重复上述过程,直到运行结束

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

def create_num(all_num):
    print("----1---")
    # a = 0
    # b = 1
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        print("----2---")
        # print(a)
        yield a  # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
        print("----3---")
        a, b = b, a+b
        current_num += 1
        print("----4---")

# 如果在调用create_num的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象
obj = create_num(3)

for num in obj:
   print(num)

运行结果:

 

当生成器模板有 return 返回值时,需要在结束异常时,使用value 得到 返回值

def create_num(all_num):
    # a = 0
    # b = 1
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        # print(a)
        yield a  # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
        a, b = b, a+b
        current_num += 1
    return "ok...."

obj = create_num(5)

while True:
    try:
        ret = next(obj)
        print(ret)
    except Exception as ret:   # 捕获异常
        print(ret.value)       # 对应 return 的值
        break

 

我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行,使用send()函数的一个好处是可以唤醒的同时向断点处传入一个附加数据。

send()方法yield a 的值对应 send()方法传递的参数 

               结果:  

不可以将send放在第一次,程序会报错,因为程序从最上面执行,没有值接收传递的参数

 

总结一下,迭代器的一大特点,可以用极小的空间、代码生成想要的数据

生成器的一大特点,yield --------- 可以让一个函数暂停执行,并且每次调用的值依旧保存,下次调用可以继续使用

 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值