文章目录
- 前言
- 一、可迭代的对象和迭代器
- 1.1 可迭代的对象
- 1.2 `iter`方法
- 1.3 迭代器
- 二、生成器
- 2.1 生成器
- 2.2 生成器函数
- 2.3 生成器表达式
- 总结
前言
在Python中,迭代器和生成器随处可见。往往在我们意识不到的时候就已经接触并使用了它们,如for循环就是在遍历迭代器,而python3的range函数会返回一个类似生成器的对象。在python中迭代器和生成器的界限很模糊,最主要的区别是迭代器是从一个容器中取出数据返回,而生成器是生成一个数据。迭代器/生成器每次只会返回/生成一个数据,所以大大节省了内存。下面就让我们来仔细了解迭代器和生成器的概念吧。
一、可迭代的对象和迭代器
students = ['john', 'ben', 'susan']
for student in students:
print(student)
john
ben
susan
上面是一个简单的for循环,那么python内部是怎么把students这个列表转化为迭代器,然后去进行遍历的呢?实际上,可以分为以下三个步骤:
- 调用内置的
iter
函数,将列表转化为迭代器 - 将迭代器作为参数,不停调用
next
函数 - 直到返回最后一个元素,然后next函数引发
StopIteration
异常,结束遍历
这里内置函数iter
方法将students(可迭代的对象)转化为迭代器,for循环遍历的是迭代器,那么什么是可迭代的对象,什么是迭代器,iter
方法是如何将两者进行转化的呢,下面让我们来逐一了解。
1.1 可迭代的对象
定义:使用iter
内置函数可以获取迭代器的对象
Python中一切皆对象,可迭代的对象是在collections.abc
模块中定义的,可迭代的对象接口协议如下:
class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
可以看到,只要实现了__iter__
方法就是一个可迭代的对象(内置函数iter
会调用__iter__
方法,该方法会返回一个可迭代的对象)。但实际上,还有另外一类对象也可以被视为可迭代的对象,它需要满足以下条件:实现了__getitem__
方法,而且其参数是从零开始的索引。
下面我们定义了一个简单的类,它只实现了__getitem__
方法,然后我们把这个类的对象传递到iter
函数中,打印iter
函数执行后返回的类型,发现也是一个迭代器。
class SimpleObj:
def __init__(self, data):
self.data = data
def __getitem__(self, i):
return self.data[i]
simple = SimpleObj(data=[1, 2, 3])
print(type(iter(simple)))
<class 'iterator'>
要理解为什么会有这种现象,我们需要知道iter
方法做了什么。
1.2 iter方法
python代码表示如下(实际上是c语言的代码),overload装饰器的作用是根据函数的传参类型,重载函数。
@overload
def iter(__iterable: Iterable[_T]) -> Iterator[_T]: ...
@overload
def iter(__function: Callable[[], Optional[_T]], __sentinel: None) -> Iterator[_T]: ...
@overload
def iter(__function: Callable[[], _T], __sentinel: Any) -> Iterator[_T]: ...
可以看到iter
函数有两种用法:
- 传入一个可迭代的对象,然后返回一个迭代器
- 传入一个可调用的对象(如不需要参数的函数,就是带
__call__
方法的对象),第二个参数是哨符,然后返回一个迭代器,当迭代的时候如果返回的是哨符,那么不返回值,结束迭代
这里我们重点讨论第一种用法,将可迭代的对象变成迭代器。
内置的iter
函数的作用是:
- 检查对象是否实现了
__iter__
方法,如果实现了就调用它,获取一个迭代器。 - 如果没有实现
__iter__
方法,但是实现了__getitem__
方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。 - 如果尝试失败,Python抛出
TypeError
异常,通常会提示“C object is not iterable”
(C对象不可迭代),其中C是目标对象所属的类。
所以只要实现了__iter__
方法或者实现了__getitem__
方法,而且其参数是从零开始的索引的对象都是可迭代的。
1.3 迭代器
迭代器同样在collections.abc
模块中定义了:
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
return _check_methods(C, '__iter__', '__next__')
return NotImplemented
迭代器是这样的对象:
- 实现了无参数的
__next__
方法,返回序列中的下一个元素;如果没有元素了,那么抛出StopIteration
异常。 - 实现了
__iter__
方法,返回self,以便在应该使用可迭代对象的地方使用迭代器,例如在for循环中。
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
二、生成器
2.1 生成器
所有生成器都是迭代器,因为生成器完全实现了迭代器接口,同样地我们可以找到生成器的协议
class Generator(Iterator):
__slots__ = ()
def __next__(self):
"""Return the next item from the generator.
When exhausted, raise StopIteration.
"""
return self.send(None)
@abstractmethod
def send(self, value):
"""Send a value into the generator.
Return next yielded value or raise StopIteration.
"""
raise StopIteration
@abstractmethod
def throw(self, typ, val=None, tb=None):
"""Raise an exception in the generator.
Return next yielded value or raise StopIteration.
"""
if val is None:
if tb is None:
raise typ
val = typ()
if tb is not None:
val = val.with_traceback(tb)
raise val
def close(self):
"""Raise GeneratorExit inside generator.
"""
try:
self.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
pass
else:
raise RuntimeError("generator ignored GeneratorExit")
@classmethod
def __subclasshook__(cls, C):
if cls is Generator:
return _check_methods(C, '__iter__', '__next__',
'send', 'throw', 'close')
return NotImplemented
它继承自迭代器,同时还有send(生成器的调用方可以使用 .send(……)方法发送数据,发送的数据会成为生成器函数中yield表达式的值)、throw(让调用方抛出指定的异常,在生成器中处理)和close(终止生成器)方法。
创建生成器的方法通常有两种,接下来让我们一一了解。
2.2 生成器函数
只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
def gen():
yield 1
yield 2
yield 3
return 4
generator = gen()
print(type(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
<class 'generator'>
1
2
3
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_4400/1214382895.py in <module>
11 print(next(generator))
12 print(next(generator))
---> 13 print(next(generator))
14
15 # generator = type((lambda: (yield))())
StopIteration: 4
- 调用生成器函数返回生成器。这里我们调用gen函数,并打印出返回值,结果是生成器类型
- 生成器产出或生成值。通过next方法我们可以推进生成器运行,并在yield操作符处停止,获取yield右边的操作数
- 生成器不会以常规的方式“返回”值:生成器函数定义体中的return语句会触发生成器对象抛出
StopIteration
异常。当next推进生成器运行到return语句时,会触发StopIteration
异常,并且可以在异常信息中获取return
返回的值
generator = type((lambda: (yield))()) # 创建类
# Generator.register(generator) # Generator源代码中注册子类
def gen():
yield 1
yield 2
yield 3
print(isinstance(gen(), generator))
True
Generator
的源码中注册了子类,这里我们可以看到,调用生成器函数返回的对象就是生成器类的实例。
2.3 生成器表达式
生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。也就是说,如果列表推导是制造列表的工厂,那么生成器表达式就是制造生成器的工厂。
gen_from_iter = (i for i in [1, 2, 3])
def gen():
yield 1
yield 2
yield 3
gen_from_gen = (i for i in gen())
print(type(gen_from_iter))
print(type(gen_from_gen))
<class 'generator'>
<class 'generator'>
可以看到,生成器表达式在括号内的for循环后面可以是一个可迭代的对象(迭代器、生成器、可迭代的对象)。
注意点:如果函数或构造方法只有一个参数,传入生成器表达式时不用写一对调用函数的括号,再写一对括号围住生成器表达式,只写一对括号就行了,
如:
Student(name for name in names)
总结
- 能被
iter
方法转化为迭代器的对象都是可迭代的对象,它包括实现了__iter__
方法以及实现了__getitem__
方法,并且参数是从零开始的索引的两类对象。 - 生成器是迭代器,从协议来看,生成器继承自迭代器,同时还需要实现其他的方法(send、close、throw)
本文主要讲解了迭代器和生成器的相关概念,后续还有两者的实际应用。