Python-迭代器和生成器

想要搞明白什么是迭代器,首先要了解几个名词:容器(container)、迭代(iteration)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)。

在这里插入图片描述

一、容器(container)

容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象)在Python中,常见的容器对象有:

  • list, deque, ….
  • set, frozensets, ….
  • dict, defaultdict, OrderedDict, Counter, ….
  • tuple, namedtuple, …
  • str
    当它可以用来询问某个元素是否包含在其中时,那么这个对象就可以认为是一个容器,比如 list,set,tuples都是容器对象。
print( 1 in [1, 2, 3])      # lists
# True
print(4 not in [1, 2, 3])
# True
print(1 in {1, 2, 3})      # sets
# True
print(4 not in {1, 2, 3})
# True
print(1 in (1, 2, 3))      # tuples
# True
print(4 not in (1, 2, 3))
# True
 
# 询问某元素是否在dict中用dict的中key:
 
d = {1: 'foo', 2: 'bar', 3: 'qux'}
print(1 in d)
# True
print('foo' not in d)  # 'foo' 不是dict中的元素
# True
 
# 询问某substring是否在string中:
 
s = 'foobar'
print('b' in s)
# True
print('x' not in s)
# True
print('foo' in s)
# True

尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有的容器都是可迭代的,比如:Bloom filter,虽然Bloom filter可以用来检测某个元素是否包含在容器中,但是并不能从容器中获取其中的每一个值,因为Bloom filter压根就没把元素存储在容器中,而是通过一个散列函数映射成一个值保存在数组中。

二 、迭代(iteration)

  • 第一,迭代需要重复进行某一操作
  • 第二,本次迭代的要依赖上一次的结果继续往下做,如果中途有任何停顿,都不能算是迭代
    示例如下:
# 实例1
# 非迭代
count = 0
while count < 10:
    print("hello world")
    count += 1
     
# 实例2
# 迭代
count = 0
while count < 10:
    print(count)
    count += 1

实例1,仅仅只是在重复一件事,那就是不停的打印"hello world",并且,这个打印的结果并不依赖上一次输出的值。而实例2,就很好地说明迭代的含义,重复+继续。

三 、可迭代对象 (iterable)

通俗的说就是在每一种数据类型对象中,都会有有一个__iter__()方法,正是因为这个方法,才使得这些基本数据类型变为可迭代。

当我们运行以下代码的时候:

x = [1,2,3]
for elem in x:
     print(elem)
      
# 运行结果:    
# 1
# 2
# 3

实际调用过程如下:
在这里插入图片描述
那么如何判断一个对象是否是可迭代呢?使用collections模块的Iterable类型判断

from collections import Iterable
 
print(isinstance('abc', Iterable)) # str是否可迭代
# True
print(isinstance([1,2,3], Iterable)) # list是否可迭代
# True
print(isinstance(123, Iterable)) # 整数是否可迭代
# False  

四 、迭代器(iterator)

通俗来讲任何具有__next__()方法的对象都是迭代器,对迭代器调用__next__()方法可以获取下一个值。
迭代是Python最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter() 和 next()。

字符串,列表或元组对象都可用于创建迭代器:

list=[1,2,3,4]
it =iter(list)      # 创建迭代器对象
print(next(it))     # 输出迭代器的下一个元素
print(next(it))

运行结果:

1
2

Process finished with exit code 0

迭代器对象可以使用常规for语句进行遍历:

list=[1,2,3,4]
it =iter(list)      # 创建迭代器对象
for x in it:
    print(x)

运行结果:

1
2
3
4

Process finished with exit code 0

也可以使用 next() 函数:

import sys         # 引入 sys 模块
list=[1,2,3,4]
it =iter(list)      # 创建迭代器对象
while True:
    try:
        print (next(it))
    except StopIteration:
        sys.exit()

运行结果:

1
2
3
4

Process finished with exit code 0

五、创建一个迭代器

把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next() 。
iter_() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。
next() 方法会返回下一个迭代器对象。
创建一个返回数字的迭代器,初始值为 1,逐步递增 1:

class MyNumbers:
    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        x = self.a
        self.a += 1
        return x

myclass = MyNumbers()
myiter = iter(myclass)
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

运行结果:

1
2
3
4
5

Process finished with exit code 0

StopIteration
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

在 20 次迭代后停止执行:

class MyNumbers:
    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        if self.a <= 20:
            x = self.a
            self.a += 1
            return x
        else:
            raise StopIteration

myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter:
  print(x)

运行结果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
Process finished with exit code 0

六、生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
yield与return返回相同的值,区别在于return返回后,函数状态终止,而yield会保存当前函数的执行状态,在返回后,函数又回到之前保存的状态继续执行。

调用一个生成器函数,返回的是一个迭代器对象。
生成器实例:

def test():
    yield 1
    yield 2
    yield 3
g=test()
print('来自函数',g)
print(g.__next__())
print(g.__next__())
 
# 运行结果
# 来自函数 <generator object test at 0x000000000072B8E0>
# 1
# 2 

生成器与一般函数的区别呢?

  • 生成器函数包含一个或者多个yield
  • 当调用生成器函数时,函数将返回一个对象,但是不会立刻向下执行
  • 像__iter__()和__next__()方法等是自动实现的,所以我们可以通过next()方法对对象进行迭代
  • 一旦函数被yield,函数会暂停,控制权返回调用者
  • 局部变量和它们的状态会被保存,直到下一次调用
  • 函数终止的时候,StopIteraion会被自动抛出
    示例:
# 简单的生成器函数
def my_gen():
     n=1
     print("first")
     # yield区域
     yield n
 
     n+=1
     print("second")
     yield n
 
     n+=1
     print("third")
     yield n
 
a=my_gen()
print("next method:")
# 每次调用a的时候,函数都从之前保存的状态执行
print
print(next(a))(next(a))
print(next(a))
 
# 运行结果
# next method:
# first
# 1
# second
# 2
# third
# 3
 
print("for loop:")
# 与调用next等价的
b=my_gen()
for elem in my_gen():
    print(elem)
 
# 运行结果
# for loop:
# first
# 1
# second
# 2
# third
# 3

使用循环的生成器:

# 逆序yield出对象的元素
def rev_str(my_str):
    length=len(my_str)
    for i in range(length-1,-1,-1):
        yield my_str[i]
 
for char in rev_str("hello"):
    print(char)
 
# 运行结果
# o
# l
# l
# e
# h

七、生成器表达式

Python中,有一个列表生成方法,也就是常说的列表解析,提到列表解析就先要弄明白三元表达式的概念,什么是三元表达式呢?

egg_list=[]
for i in range(10):
    egg_list.append('鸡蛋%s' %i)
print(egg_list)
# ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
 
# 使用三元表达式替换如上代码
l=['鸡蛋%s' %i for i in range(10)]
print(l)
# ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
 
l1=['鸡蛋%s' %i for i in range(10) if i > 5 ]
print(l1)
# ['鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
 
# l2=['鸡蛋%s' %i for i in range(10) if i > 5 else i] #没有四元表达式
# print(l2)
 
l3=['鸡蛋%s' %i for i in range(10) if i < 5]
print(l3)
# ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4']

了解了三元表达式,我们再来看看什么是生成器表达式,其实很简单,就是把三元表达式中的[]换成()即可。

a=(x for x in range(10))
b=[x for x in range(10)]
# 这是错误的,因为生成器不能直接给出长度
# print("length a:",len(a))
 
# 输出列表的长度
print("length b:",len(b))
# length b: 10
b=iter(b)
# 二者输出等价,不过b是在运行时开辟内存,而a是直接开辟内存
print(next(a))
print(next(b))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值