Python迭代器、生成器及yield关键字

一、迭代

  1. 迭代:从对象中,逐个获取元素的过程。

  2. 可迭代对象
    理解:可以进行迭代操作的对象;
    定义:含有 __iter__() 方法或 __getitem__() 方法的对象;
    常见可迭代对象:字典(dict)、元组(tuple)、集合(set)和字符串等;
    可迭代是对象的一种特性,可迭代对象,都可以进行for循环进行遍历;
    可以使用函数hasattr()isinstance()判断一个对象是否可迭代。

from collections import Iterable

a = [1, 2, 3]
b = 'python'

print(hasattr(a, '__iter__'))
True

print(isinstance(b, Iterable))
True


二、迭代器

  1. 定义:指遵循迭代器协议(iterator protocol)的对象。

  2. 迭代器协议(iterator protocol):实现__iter__()方法和__next__()方法。__iter__()方法返回迭代器对象本身,__next__()方法返回容器的下一个元素,没有后续元素时抛出 StopIteration 异常。

  3. 迭代器是访问序列内元素的一种方式,提供了一种遍历序列对象的方法。

  4. 可以使用函数hasattr()isinstance()判断一个对象是否是迭代器。

from collections import Iterable, Iterator 

a = [1, 2, 3]
b = 'python'

print(hasattr(a, '__iter__'))
True

print(hasattr(a, '__next__'))   #有 __iter__ 方法但是没有 __next__ 方法,不是迭代器
False

print(isinstance(a, Iterator))
False
  1. 虽然字符串、元组、列表和字典等对象都是可迭代的,但它们却不是迭代器。这些可迭代对象,可以使用 Python 内置的 iter() 函数获得它们的迭代器对象。
from collections import Iterable, Iterator 

a = [1, 2, 3]
b = 'python'

print(isinstance(iter(a), Iterator))
True

print(isinstance(iter(b), Iterator))
True
  1. 对Python中的可迭代对象,进行 for 循环遍历时,就是先通过内置函数 iter() 获得一个迭代器,然后再不断调用 __next__() 函数进行迭代。

三、生成器

  1. 生成器(generator)是一个返回迭代器(iterator)的函数。

  2. 生成器只能用于迭代操作,且只能迭代一次,因为值是在迭代的过程生成的,而所有的值并没有保存在内存中。

  3. 简单点理解,生成器就是一个特殊的迭代器,每次迭代时返回一个值,直到抛出 StopIteration 异常。

  4. 构造生成器的方法

  • 生成器表达式
    列表推导式 的定义类似,生成器表达式使用圆括号() 而不是方括号[],比如:
nums = (x for x in range(4))

for num in nums:
    print(num, end = ' ')

0 1 2 3 
  • 生成器函数(yield)
    调用含有 yield 关键字的函时,会返回一个生成器;
    实际上,yield 仅能用于定义生成器函数。
  1. 生成器在Python中是一个非常强大的编程结构,可以用更少地中间变量写流式代码,更能节省内存和CPU,同时可以用更少的代码来实现相似的功能。

  2. 生成器小结
    是可迭代对象;
    延迟计算,一次返回一个结果,节省内存;
    本质上和其他的数据类型一样,都是实现了迭代器协议。

四、生成器函数(yield)

  1. 生成器函数,不使用 return 语句返回值,而使用关键字 yield,后跟一个表达式或值,调用时返回该表达式或值。

  2. 在生成器函数中,关键字 yield会在内部自动创建__iter__()__next__()方法,而且在没有数据时,也会抛出 StopIteration 异常,非常简单高效地获得一个迭代器。

  3. 执行过程
    调用生成器函数时,不会立即执行代码,而是返回一个生成器对象;
    __next__()方法作用于返回的生成器对象时,函数开始执行;
    每次遇到 yield 时,函数会暂停并保存当前所有的运行信息,返回 yield 表达式的值;
    当下一次执行 __next__() 方法时,函数会从原来暂停的地方继续执行;
    直到没有 yield 时,抛出异常;
    简而言之,就是 __next__() 使函数执行,yield 使函数暂停。

def generator_function():
    print('Hello 1')
    yield 1
    print('Hello 2')
    yield 2
    print('Hello 3')

g = generator_function()

print(g)    # 函数没有立即执行,而是返回了一个生成器,当然也是一个迭代器
<generator object generator_function at 0x109f02c50>

print(g.__next__())     # 当使用__next()__方法,或使用 next(g) 时开始执行,遇到 yield 暂停
Hello 1
1

print(g.__next__())     # 从原来暂停的地方继续执行
Hello 2
2

print(g.__next__())     # 从原来暂停的地方继续执行,没有 yield,抛出异常
Hello 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
  1. Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的,可以直接对迭代器进行函数操作,比如sum()list()等。
a = [1, 5, 2, 1, 9, 1, 5, 10]

def dedupe(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

# 返回生成器对象
print(type(dedupe(a)))
<class 'generator'>

# 不同值求和
print(sum(dedupe(a)))
27

# 删除序列中相同元素,并保持原来的顺序
print(list(dedupe(a)))
[1, 5, 2, 9, 10]
  1. 注意事项
    迭代器函数可以多次调用(见上述4中的示例);
    但一个生成器对象只能遍历一次;
    如下示例中,执行sum()函数时,遍历了生成器d;执行list()函数,再次遍历生成器d时,将不会有任何记录,结果为空。
a = [1, 5, 2, 1, 9, 1, 5, 10]

def dedupe(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

d = dedupe(a)    # d 为一个生成器对象

# d 为生成器对象
print(type(d))
<class 'generator'>

# 不同值求和
print(sum(d))
27

# 删除序列中相同元素,并保持原来的顺序:结果为空
print(list(d))
[]
  1. 示例:生成一个Fibonacci 数列
  • 自定义迭代器
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        return self.a

f = Fib()
for item in f:
    if item > 10:
        break
    print(item, end=' ')

1 1 2 3 5 8 
  • 生成器函数
def fib():
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield a 

f = fib()
for item in f:
    if item > 10:
        break
    print(item, end=' ')

1 1 2 3 5 8 

可以看到,使用生成器函数的方法非常简洁,不用自定义 __iter__()__next__() 方法。

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