python第二十一节可迭代对象迭代器生成器

本文详细介绍了Python中的可迭代对象、迭代器和生成器的概念,包括它们的判断方法,自定义可迭代对象的实现,迭代器和生成器的协议要求,以及它们在for循环中的应用。特别强调了生成器的特殊性,如yield语句和内存节省特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

判断Iterable,Iterator,Generator

from typing import Iterable, Iterator, Generator
num1 = 123
str1 = "abc"
list1 = [1, 2, 3]
tup1 = (1, 2, 3)
dict1 = {"one": 1, "two": 2, "three": 3}
set1 = {1, 2, 3}
"""Python标准数据类型中, 除了number, 其他都是可迭代对象, 即可以用for循环来遍
历"""
print(isinstance(num1, Iterable)) # False
print(isinstance(str1, Iterable)) # True
print(isinstance(list1, Iterable)) # True
print(isinstance(tup1, Iterable)) # True
print(isinstance(dict1, Iterable)) # True
print(isinstance(set1, Iterable)) # True

print(isinstance(num1, Iterator)) # False
print(isinstance(str1, Iterator)) # False
print(isinstance(list1, Iterator)) # False
print(isinstance(tup1, Iterator)) # False
print(isinstance(dict1, Iterator)) # False
print(isinstance(set1, Iterator)) # False

print(isinstance(num1, Generator)) # False
print(isinstance(str1, Generator)) # False
print(isinstance(list1, Generator)) # False
print(isinstance(tup1, Generator)) # False
print(isinstance(dict1, Generator)) # False
print(isinstance(set1, Generator)) # False

""" range对象是可迭代对象 """
range1 = range(4)
print(isinstance(range1, Iterable))
""" reversed对象是迭代器 """
reversed1 = reversed([1, 3, 1, 4])
print(isinstance(reversed1, Iterator))
""" zip对象是迭代器 """
zip1 = zip([1, 2, 3], ["one", "two", "three"])
print(isinstance(zip1, Iterator))
""" enumerate对象是迭代器 """
enumerate1 = enumerate([1, 2, 3])
print(isinstance(enumerate1, Iterator))

可迭代对象(Iterable)

只要满足以下条件之一即可:

  • 支持迭代协议(有__iter__()方法)
  • 支持序列协议(有__getitem__()方法,且数字参数从0开始)
num1 = 123
str1 = "abc"
list1 = [1, 2, 3]
tup1 = (1, 2, 3)
dict1 = {"one":1, "two":2, "three":3}
set1 = {1, 2, 3}
range1 = range(4)


""" str, list, tuple, dict, set, range对象都支持迭代协议, 所以他们都是可迭
代对象而 number 对象既不支持迭代协议,也不支持序列协议,因此它不是可迭代对象 """
print('__iter__' in dir(num1) or '__getitem__' in dir(num1))  # False
print('__iter__' in dir(str1))  # True
print('__iter__' in dir(list1))  # True
print('__iter__' in dir(tup1))  # True
print('__iter__' in dir(dict1))  # True
print('__iter__' in dir(set1))  # True
print('__iter__' in dir(range1))  # True

自定义可迭代对象

  • 只要支持迭代协议或者序列协议即可
from typing import Iterable
class MyObject1:
	def __init__(self):
		pass


mo1 = MyObject1()
print(isinstance(mo1, Iterable)) # False: 既不支持迭代协议, 也不支持序列协议
class MyObject2:

	def __init__(self):
		pass
		
	def __iter__(self):
		pass


mo2 = MyObject2()
print(isinstance(mo2, Iterable))  # True: 因为有__iter__()方法, 即支持迭代协议


class MyObject3:
	def __init__(self):
		pass
		
	def __getitem__(self, index):
		pass


mo3 = MyObject3()
print(isinstance(mo3, Iterable)) # False
"""
mo3支持序列协议, 为何 isinstance 判断为 False 呢?
注意: 使用 isinstance(obj, Iterable) 可以检测obj是否有__iter__()方法,
但是无法检测是否能够使用__getitem__()方法进行迭代
"""

迭代器(Iterator)

支持迭代器协议(注意区分迭代协议),即同时满足下面两个条件:

  • 实现__iter__()方法
  • 实现__next__()方法
reversed1 = reversed([1, 3, 1, 4])
zip1 = zip([1, 2, 3], ["one", "two", "three"])
enumerate1 = enumerate([1, 2, 3])

print('__iter__' in dir(reversed1) and '__next__' in dir(reversed1))  # true
print('__iter__' in dir(zip1) and '__next__' in dir(zip1))  # true
print('__iter__' in dir(enumerate1) and '__next__' in dir(enumerate1))  # true

自己创建迭代器

  • 只要支持迭代器协议即可
from typing import Iterator


class MyObject1:
	def __init__(self):
		pass


mo1 = MyObject1()
print(isinstance(mo1, Iterator)) # False: 不支持迭代器协议


class MyObject2:
	def __init__(self):
		pass
	def __iter__(self):
		pass


mo2 = MyObject2()
print(isinstance(mo2, Iterator)) # False: 不支持迭代器协议


class MyObject3:
	def __init__(self):
		pass
	def __iter__(self):
		pass
	def __next__(self):
		pass


mo3 = MyObject3()
print(isinstance(mo3, Iterator)) # True: 支持迭代器协议

迭代的逻辑

  • 迭代的时候,先找__iter__()方法,如果没有的话,再找__getitem__()方法
  • 如果有__iter__()方法:
    • 可迭代对象里的__iter__()方法返回一个迭代器,通过迭代器里的__next__()方法实现迭代
    • 迭代器里的__iter__()方法返回它本身(因为迭代器协议中包含了迭代协议,所以迭代器也一定是可迭代对象,可迭代对象的__iter__()方法要返回一个迭代器,所以只需要返回本身即可)
    • 迭代器里的__next__()方法返回可迭代对象的下一项,如果没有下一项可返回,则抛出 StopIteration 异常
class ContainerIterator:

    def __init__(self, container):
        self.container = container
        self.cursor = 0


def __iter__(self):  # 对应第 ② 点

    return self


def __next__(self):  # 对应第 ③ 点

    if self.cursor < len(self.container.iterable):
        item = self.container.iterable[self.cursor]
        self.cursor += 1
        return item
    raise StopIteration


class Container:

    def __init__(self, iterable):
        self.iterable = iterable

    def __iter__(self):  # 对应第 ① 点
        return ContainerIterator(self)


cont = Container([4, 5, 6])
"""
for语句在执行时会先调用可迭代对象的__iter__()方法, 得到该方法返回的迭代器对象,
然后每循环一次, 该迭代器对象调用一次__next__()方法, 通过__next__()方法来逐一
返回元素,
当元素用尽时, __next__()方法将抛出StopIteration异常, 而for语句会捕获这个异常
来break循环
 """
for item in cont:
    print(item)
""" for循环原理实现 """
cont_iterator = cont.__iter__()
while True:
    try:
        item = cont_iterator.__next__()
        print(item)
    except StopIteration:
        break
    """
    除了for循环以外, 其他能够接收iterable参数的函数或方法大多都是基于类似原理,
    比如: list()、tuple()、set()、sum()等等 """
    print(list(cont))
    print(tuple(cont))
    print(set(cont))
    print(sum(cont))
    """
    迭代器也是可迭代对象, for语句在执行时会先调用迭代器的__iter__()方法,
    而迭代器的__iter__()方法正好返回迭代器对象本身, 后面过程就是一样的了"""
cont_iterator = cont.__iter__()
for k in cont_iterator:
    print(k)

如果只有__getitem__()方法:

class Container:
    def __init__(self, iterable):
        self.iterable = iterable

    def __getitem__(self, index):
        return self.iterable[index]


cont = Container([5, 6, 7])
"""
for语句在执行时, 先找__iter__()方法, 没有找到, 则找__getitem__()方法, 然后
每循环一次, 调用一次__getitem__()方法, 其中index参数从0开始, 无限递增+1, 当
index超出iterable索引范围抛出IndexError, for语句会捕获这个异常来break循环
"""
for i in cont:
    print(i)
""" list()、tuple()、set()、sum()等也是基于类似原理 """
print(list(cont))
print(tuple(cont))
print(set(cont))
print(sum(cont))

""" 测试__getitem__()方法中的index参数 """


class MyObject:
    def __init__(self):
        pass

    def __getitem__(self, index):
        if index > 3:
            raise IndexError
        return index


mo = MyObject()
for i in mo:
    print(i)
print(list(mo))
print(tuple(mo))
print(set(mo))

生成器(Generator)

  • 生成器写法类似于标准的函数写法,不同点在于:
    • 生成器用 yield 语句返回数据,而标准的函数用 return 语句返回数据
    • yield 语句返回数据之后会挂起函数的状态,并会记住上次执行语句时的所有数据值,方便每次在生成器调用__next__()方法时,从上次挂起的位置恢复继续执行,而 return 语句返回一次数据之后,函数就结束了
from collections import Generator, Iterator


# return 出现一次就直接结束函数
def func():
	print("The function body starts executing...")
	return 2
	print("The function body continues execution...")
	return 4
	print("ending...")


res = func()
print(res)

ef gen():
    print("The function body starts executing...")
    yield 2
    print("The function body continues execution...")
    yield 4
    print("ending...")


"""
包含yield关键字的函数就是生成器函数, 调用生成器函数不会执行函数体代码,
而是直接返回一个生成器对象, 调用__next__()方法才会开始执行函数体代码 """
g = gen()  # 返回生成器对象
print(isinstance(g, Generator))  # True
print("__iter__" in dir(g) and "__next__" in dir(g))  # True: 生成器实现了迭代器协议
print(isinstance(g, Iterator))  # True: 生成器一定也是迭代器
"""
生成器对象调用__next__()方法就会开始执行函数体代码, 遇到 yield 则把
后面的数据返回, 然后挂起函数的状态, 直到下一次调用__next__()方法, 再
从挂起的状态继续往后执行, __next__()方法如果没有可以返回的值, 会抛出
StopIteration异常, 直到函数体执行完毕, 函数才算结束 """
print(g.__next__())
print(g.__next__())
""" 从上一步挂起状态继续往后执行, 输出: ending...
此时__next__()没有可以返回的值, 抛出 StopIteration 异常
函数体执行完毕, 函数结束 """
# print(g.__next__())
g2 = gen()  # g迭代完毕, 再建一个生成器
"""
生成器也是迭代器, 所以和迭代器逻辑是一样的, for语句在执行时会
先调用__iter__()方法, 返回self, 然后每次循环时, self调用一次
__next__()方法, 因为调用了该方法, 函数体就会开始执行, 执行到
yield, 把后面的数据返回, 然后函数挂起, 当循环又调用__next__()
方法时, 则从函数挂起处继续执行函数体, 而当没有 yield 时, 即没
有返回数据时, __next__()方法将引发 StopIteration 异常, for语
句会捕获这个异常来 break 循环 """
for i in g2:
    print(i)
""" list()、tuple()、set()、sum()等也是基于类似原理 """
print(list(g2))
print(tuple(g2))
print(set(g2))
print(sum(g2))

生成器表达式

  • 生成器表达式所用语法类似列表推导式,只是外层为圆括号而非方括号
  • 生成器表达式相比完整的生成器函数来说更紧凑,相比列表推导式则更为节省内存,因为列表推导式是一次构建一个结果列表,而生成器表达式返回的是一个生成器,再根据对生成器的处理函数按需迭代产生结果
# 列表推导式
list1 = [i for i in range(5)]
print(type(list1)) # <class 'list'>
print(sum(list1)) # 10
print(sum(list1)) # 10


# 生成器表达式
gt1 = (i for i in range(5))
print(type(gt1)) # <class 'generator'>

"""
这里gt1第二次sum结果为什么是0, 而上面的list1却不是?
因为同一个gt1生成器对象, 第一次sum已经迭代完了, 而sum默认从0开始累加, 结果就为0
而每次sum(list1)都会通过list的__iter__()方法返回新的迭代器, 并不是同一个 """
print(sum(gt1)) # 10
print(sum(gt1)) # 0
""" 生成器表达式如果立即被外层的函数使用, 可以省略圆括号,
而不用写成 sum((i for i in range(5))) """
print(sum(i for i in range(5))) # 10

iter(object[,sentinel])

  • 返回一个迭代器对象
  • 如果没有第二个实参,object必须支持迭代协议(有__iter__()方法)或序列协议(有__getitem__()方法,且数字参数从0开始)。如果它不支持这些协议,会触发
    TypeError
  • 如果有第二个实参sentinel,那么object必须是可调用的(函数、方法、lambda匿名函数、 类以及实现了 call ()方法的实例对象)。这种情况下生成的迭代器,每次迭代调用它的__next__()方法时都会不带实参地调用object,返回调用的结
    果,如果返回的结果是sentinel,则触发StopIteration
str1 = "abc"
list1 = [1, 2, 3]
tup1 = (1, 2, 3)
dict1 = {"one": 1, "two": 2, "three": 3}
set1 = {1, 2, 3}
# str1、list1、tup1、dict1、set1 支持迭代协议
print(iter(str1))
print(iter(list1))
print(iter(tup1))
print(iter(dict1))
print(iter(set1))
class MyObject:
	def __init__(self):
		pass
	def __getitem__(self, index):
		pass
# MyObject() 支持序列协议
print(iter(MyObject()))


class MyObject1:
	def __init__(self):
		self.num = 3
		
	def __call__(self):
		self.num += 1
	return self.num


"""
有第二个参数 sentinel = 7, 且 MyObject1() 是可调用对象, 则每次迭代时
调用 MyObject1()() 即调用 __call__方法, 当结果为 sentinel 时,
触发StopIteration, 被list捕获, 停止迭代 """
call_iter = iter(MyObject1(), 7)
print(call_iter)
print(list(call_iter)) # [4, 5, 6]

next(iterator[, default])

  • 通过调用 iterator 的 next() 方法获取下一个元素。如果迭代器耗尽,则返回给定的 default,如果没有默认值则触发 StopIteration
str_iterator = iter("abcd")
print(next(str_iterator)) # "a"
print(next(str_iterator)) # "b"
print(next(str_iterator)) # "c"
print(next(str_iterator)) # "d"
print(next(str_iterator, "ef")) # 迭代耗尽, 返回 "ef"
print(next(str_iterator)) # 迭代耗尽, 触发 StopIteration

迭代器的优缺点
迭代器优点:

  • 提供了一种不依赖索引的迭代取值方式
  • 节省内存,迭代器在内存中相当于只占一个数据的空间:因为每次取值上一条数据都会在内存释放,再加载当前的此条数据,而不需要一次性把所有数据加载到内存当中
  • 执行列表推导式:sum([i for i in range(10000000000)]) 的内存使用情况
    在这里插入图片描述
  • 执行生成器表达式:sum(i for i in range(10000000000)) 的内存使用情况
    在这里插入图片描述
    迭代器缺点:
  • 取值不如按照索引的方式灵活,不能取指定的某一个值,只能往后取,不能往前去
  • 除非取尽,否则无法获取迭代器的长度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙_尧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值