Python高级语法:生成器(Generator)

Generator

1. iterable&iterator

​ iterable是可迭代对象,iterator是迭代器,在了解generator之前有必要了解一下这两个概念。

​ 可迭代对象(iterable)是能够返回迭代器的对象,换句话说,只要一个对象实现了 __iter__() 方法 或者一个 __gettiem__() 方法(注意:__getitem__() 能被当作 iterable 是 Python 的一个兼容特性,现代推荐用 __iter__() 明确表示可迭代),那它就是可迭代对象。常见的 iterable 包括列表、元组、字典、集合、字符串等,都是可迭代对象。

my_list = [1, 2, 3]  # 这是一个可迭代对象

# 方式一:借助 iter() 函数获取迭代器
iter_obj = iter(my_list)

# 方式二:在 for 循环内部自动调用 iter()
for item in my_list:
    print(item)

​ 迭代器(iterator)是实现了__iter__()__next__() 方法的对象,其中__iter__()方法会返回迭代器自身,而 __next__()方法则会返回序列中的下一个元素。当序列中没有元素可供返回的时候,__next__()方法会抛出 StopIteration 异常。迭代器的主要作用就是记录迭代状态。下面我自定义了一个迭代器,可以结合上面说的来了解:

class EvenNumber:
    def __init__(self, max_num):
        self.max_num = max_num
        self.current = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current >= self.max_num:
            raise StopIteration
        
        result = self.current
        self.current += 2
        return result

evens = EvenNumber(10)
for num in evens:
    print(num)
特性可迭代对象迭代器
核心方法__iter__()__getitem__()__iter__()__next__()
能否使用 iter()可以可以(返回迭代器自身)
能否使用 next()不可以可以
状态记录不记录记录当前迭代位置
能否多次迭代通常可以不可以(只能迭代一次)

​ 有关 iterable 和 iterator,为什么 iterable 能够多次循环调用,而 iterator 只能一次?实际上是因为每次调用 iter() 会返回新的 iterator,因此可以多次迭代;iterator:迭代一次之后就 exhausted(耗尽)

2. generator

​ 可以看到,如果我想写一个自定义的迭代器,是非常麻烦的,又要定义类,又要自定义 __iter__()__next__(),那么,有没有一种方法可以更简便的来实现迭代器呢?这就是 generator 所解决的问题了。

​ 要了解 generator,首先需要了解 yield 的用法,来看下面一段代码:

def s1():
    return "s1"

def s2():
    return "s2"

def s3():
    return "s3"

def hello():
    print("step 01")
    yield s1()
    print("step 02")
    yield s2()
    print("step 03")
    yield s3()

g = hello()
print(g)
# <generator object hello at 0x0000028F0D294D00>
print(next(g))
# step 01
# s1
print(next(g))
# step 02
# s2
print(next(g))
# step 03
# s3

​ 程序在被调用之后,会返回一个 generator,也就是 g,代码中的 变量 g 在接收 hello() 之后就变成了一个生成器对象,在这一行代码执行之后,实际上如果你直接 print(g) 的话,会得到的结果是 g 这个 generator 对象,而不会得到任何返回值,另外,执行 g = hello() 的时候,实际上 hello() 函数一句都不会执行。

yieldreturn的区别也在于此,当函数执行到 return 的时候会直接结束,但是执行到 yield 的时候则不会,直到函数返回的 generator 被 next()方法调用的时候,才会继续往下走。

​ 那么其实讲到这里,就可以发现,genarator 可以简化 iterator 的写法,这就是 generator 的第一个作用。

​ generator 的第二个作用,是它可以极大地优化内存,具有很高的性能。为什么这么说?因为 generator 是一个典型的 Lazy Object,它只有在被调用的时候才会执行。上面也讲到了,上面例子中函数执行到 g = hello() 的时候,实际上 hello() 这个函数一句都没有执行,而普通函数的话在使用 g = hello() 的时候,实际上函数已经跑一遍了。generator 的这个特性使得它能够优化内存,优化性能。

​ 关于性能优化方面,这里举一个具体的例子可以加深体会,如下:

def read_large_file(filename):
    with open(filename, 'r') as f:
        for line in f:
            yield line

# 逐行处理,不占用大量内存
for line in read_large_file('big.txt'):
    process(line)

​ 显然,如果不使用 generator,会一次性把所有内容全部读到内存,而如果使用 generator,每次只会读一行。

3. 补充:yield 和 return 的区别

​ 前面讲这两者的区别其实讲的不是完全正确,这里展开讲一下。

return:用于普通函数,立即结束函数执行并返回一个值。

yield:用于生成器函数,暂停函数执行,返回一个值,并保留函数当前状态,下次继续执行。

特性returnyield
用于的函数类型普通函数生成器函数(generator)
函数调用后返回立即执行,返回一个值返回一个生成器对象(不执行函数体)
是否结束函数✅ 结束函数执行❌ 不结束,挂起函数执行
返回什么一个值(任意类型)每次迭代一个值
多次使用时只能返回一次可以多次挂起和恢复,每次返回一个新值
执行方式函数一旦调用就完整执行需用 next() 推进一步步执行
内部状态保留❌ 不保留,return 后局部变量都销毁✅ 保留局部变量、执行位置
适合场景一次性返回一个结果惰性计算、流式处理、大数据、迭代器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Way2top

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

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

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

打赏作者

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

抵扣说明:

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

余额充值