Python 玩转迭代器、生成器(上)


前言

在Python中,迭代器和生成器随处可见。往往在我们意识不到的时候就已经接触并使用了它们,如for循环就是在遍历迭代器,而python3的range函数会返回一个类似生成器的对象。在python中迭代器和生成器的界限很模糊,最主要的区别是迭代器是从一个容器中取出数据返回,而生成器是生成一个数据。迭代器/生成器每次只会返回/生成一个数据,所以大大节省了内存。下面就让我们来仔细了解迭代器和生成器的概念吧。


一、可迭代的对象和迭代器

students = ['john', 'ben', 'susan']
for student in students:
    print(student)
john
ben
susan

上面是一个简单的for循环,那么python内部是怎么把students这个列表转化为迭代器,然后去进行遍历的呢?实际上,可以分为以下三个步骤:

  1. 调用内置的iter函数,将列表转化为迭代器
  2. 将迭代器作为参数,不停调用next函数
  3. 直到返回最后一个元素,然后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函数有两种用法:

  1. 传入一个可迭代的对象,然后返回一个迭代器
  2. 传入一个可调用的对象(如不需要参数的函数,就是带__call__方法的对象),第二个参数是哨符,然后返回一个迭代器,当迭代的时候如果返回的是哨符,那么不返回值,结束迭代

这里我们重点讨论第一种用法,将可迭代的对象变成迭代器。
内置的iter函数的作用是:

  1. 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器。
  2. 如果没有实现__iter__方法,但是实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。
  3. 如果尝试失败,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

迭代器是这样的对象:

  1. 实现了无参数的__next__方法,返回序列中的下一个元素;如果没有元素了,那么抛出StopIteration异常。
  2. 实现了__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
  1. 调用生成器函数返回生成器。这里我们调用gen函数,并打印出返回值,结果是生成器类型
  2. 生成器产出或生成值。通过next方法我们可以推进生成器运行,并在yield操作符处停止,获取yield右边的操作数
  3. 生成器不会以常规的方式“返回”值:生成器函数定义体中的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)

总结

  1. 能被iter方法转化为迭代器的对象都是可迭代的对象,它包括实现了__iter__方法以及实现了__getitem__方法,并且参数是从零开始的索引的两类对象。
  2. 生成器是迭代器,从协议来看,生成器继承自迭代器,同时还需要实现其他的方法(send、close、throw)
    本文主要讲解了迭代器和生成器的相关概念,后续还有两者的实际应用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

海夜风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值