Python 可迭代对象、迭代器、生成器

1.可迭代的对象

定义:可迭代的对象(Iterable)是指使用iter()内置函数可以获取迭代器(Iterator)的对象

  • Python解释器需要迭代对象x时,会自动调用iter(x),内置的iter()函数有以下作用:
    (1) 检查对象x是否实现了iter()方法,如果实现了该方法就调用它,并尝试获取一个迭代器
    (2) 如果没有实现iter()方法,但是实现了getitem(index)方法,尝试按顺序(从索引0开始)获取元素,即参数index是从0开始的整数(int)。之所以会检查是否实现getitem(index)方法,为了向后兼容
    (3) 如果前面都尝试失败,Python会抛出TypeError异常,通常会提示'X' object is not iterable(X类型的对象不可迭代),其中X是目标对象所属的类
  • 具体来说,哪些是可迭代对象呢?
    (1) 如果对象实现了能返回迭代器的iter()方法,那么对象就是可迭代的
    (2) 如果对象实现了getitem(index)方法,而且index参数是从0开始的整数(索引),这种对象也可以迭代的。Python中内置的序列类型,如list、tuple、str、bytes、dict、set、collections.deque等都可以迭代,原因是它们都实现了getitem()方法(注意: 其实标准的序列还都实现了iter()方法)

1.1 判断对象是否可迭代

检查对象x能否迭代,最准确的方法是:调用iter(x)函数,如果不可迭代,会抛出TypeError异常。这比使用isinstance(x, abc.Iterable)更准确,因为iter(x)函数会考虑到遗留的__getitem__(index)方法,而abc.Iterable类则不会考虑

1.2 __getitem__()

下面构造一个类,它实现了__getitem__()方法。可以给类的构造方法传入包含一些文本的字符串,然后可以逐个单词进行迭代:

'''创建test.py模块'''
import re
import reprlib

RE_WORD = re.compile('\w+')

class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

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

    def __len__(self):  # 为了让对象可以迭代没必要实现这个方法,这里是为了完善序列协议,即可以用len(s)获取单词个数
        return len(self.words)
    def __repr__(self):
        return 'Sentence({})'.format(reprlib.repr(self.text))

测试Sentence实例能否迭代:

In [1]: from test import Sentence  # 导入刚创建的类

In [2]: s = Sentence('I love Python')  # 传入字符串,创建一个Sentence实例

In [3]: s
Out[3]: Sentence('I love Python')

In [4]: s[0]
Out[4]: 'I'

In [5]: s.__getitem__(0)
Out[5]: 'I'

In [6]: for word in s:  # Sentence实例可以迭代
   ...:     print(word)
   ...:     
I
love
Python

In [7]: list(s)  # 因为可以迭代,所以Sentence对象可以用于构建列表和其它可迭代的类型
Out[7]: ['I', 'love', 'Python']

In [8]: from collections import abc

In [9]: isinstance(s, abc.Iterable)  # 不能正确判断Sentence类的对象s是可迭代的对象
Out[9]: False

In [10]: iter(s)  # 没有抛出异常,返回迭代器,说明Sentence类的对象s是可迭代的
Out[10]: <iterator at 0x7f82a761e5f8>

1.3. __iter__()

用法:
__iter__()
\t return 返回迭代器

1.4 iter()函数的补充

iter()函数有两种用法:

  • iter(iterable) -> iterator: 传入可迭代的对象,返回迭代器
  • iter(callable, sentinel) -> iterator: 传入两个参数,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出 StopIteration 异常,而不产出哨符

2. 迭代器

一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)

迭代器是这样的对象:实现了无参数的__next__()方法,返回序列中的下一个元素,如果没有元素了,就抛出StopIteration异常。即,迭代器可以被next()函数调用,并不断返回下一个值

在 Python 语言内部,迭代器用于支持: for 循环。构建和扩展集合类型逐行遍历文本文件列表推导、字典推导和集合推导元组拆包调用函数时,使用 * 拆包实参

2.1 判断对象是否为迭代器

检查对象x是否为迭代器最好的方式是调用isinstance(x, abc.Iterator)
Python中内置的序列类型,如list、tuple、str、bytes、dict、set、collections.deque等都是可迭代的对象,但不是迭代器; 生成器一定是迭代器

2.2 __next__()__iter__()

标准的迭代器接口:

  • __next__(): 返回下一个可用的元素,如果没有元素了,抛出StopIteration异常。调用next(x)相当于调用x.__next__()
  • __iter__(): 返回迭代器本身(self),以便在应该使用可迭代的对象的地方能够使用迭代器,比如在for循环、list(iterable)函数、sum(iterable, start=0, /)函数等应该使用可迭代的对象地方可以使用迭代器。说明: 只要实现了能返回迭代器的__iter__()方法的对象就是可迭代的对象,所以,迭代器都是可迭代的对象!

2.3 next()函数获取迭代器中下一个元素

除了可以使用for循环处理迭代器中的元素以外,还可以使用next()函数,它实际上是调用iterator.__next__(),每调用一次该函数,就返回迭代器的下一个元素。如果已经是最后一个元素了,再继续调用next()就会抛出StopIteration异常。一般来说,StopIteration异常是用来通知我们迭代结束的
或者,为next()函数指定第二个参数(默认值),当执行到迭代器末尾后,返回默认值,而不是抛出异常

2.4 可迭代的对象与迭代器的对比

  • 可迭代的对象迭代器之间的关系:Python从可迭代的对象中获取迭代器
  • for循环迭代一个字符串'ABC',字符串是可迭代的对象for循环的背后会先调用iter(s)将字符串转换成迭代器,只不过我们看不到
  • StopIteration异常表明迭代器到头了,Python语言内部会处理for循环和其它迭代上下文(如列表推导、元组拆包等)中的StopIteration异常

总结:
(1) 迭代器要实现__next__()方法,返回迭代器中的下一个元素
(2) 迭代器还要实现__iter__()方法,返回迭代器本身,因此,迭代器可以迭代。迭代器都是可迭代的对象
(3) 可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现__iter__()方法,但不能实现__next__()方法

3. 生成器

在Python中,可以使用生成器让我们在迭代的过程中不断计算后续的值,而不必将它们全部存储在内存中

3.1 生成器函数

  • 只要 Python 函数的定义体中有 yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器(generator)对象。也就是说,生成器函数是生成器工厂
  • 普通的函数生成器函数在语法上唯一的区别是,在后者的定义体中有yield关键字
  • 调用生成器函数后会创建一个新的生成器对象,但是此时还不会执行函数体。
  • next(g)时,会激活生成器,生成器函数会向前前进到 函数定义体中的下一个 yield 语句生成 yield关键字后面的表达式的值,在函数定义体的当前位置暂停,并返回生成的值
  • 注意用词: 普通函数返回值,调用生成器函数返回生成器,生成器产出或生成值
  • 调用生成器函数后,会构建一个实现了迭代器接口的生成器对象,即,生成器一定是迭代器!
  • 迭代器生成器都是为了惰性求值(lazy evaluation),避免浪费内存空间

3.2 生成器表达式

  • 简单的生成器函数(有yield关键字),可以替换成生成器表达式(没有yield关键字,将列表推导中的[]替换为()即可),让代码变得更简短
  • 生成器表达式可以理解为列表推导的惰性版本不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。也就是说,如果列表推导是制造列表的工厂,那么生成器表达式就是制造生成器的工厂
  • 生成器管道(pipeline)一样链接起来使用,更高效的处理数据

3.3 增强生成器

使用.send()方法
使用方法:对象 . send(x) #x 为发送的数据
.__next__()方法一样,.send()方法使生成器前进到下一个yield语句。不过,.send()方法还允许调用方把数据发送给生成器,即不管传给.send()方法什么参数,那个参数都会成为生成器函数定义体中对应的yield表达式的值。也就是说,send()方法允许在调用方生成器之间双向交换数据,而.__next__()方法只允许调用方从生成器中获取数据

查看生成器对象的状态:
可以使用 inspect.getgeneratorstate(...)函数查看生成器对象的当前状态:

  • 'GEN_CREATED': 等待开始执行
  • 'GEN_RUNNING': 正在被解释器执行。只有在多线程应用中才能看到这个状态
  • 'GEN_SUSPENDED': 在yield表达式处暂停
  • 'GEN_CLOSED': 执行结束

3.4 yield from

yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容