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() 函数一句都不会执行。
yield
和return
的区别也在于此,当函数执行到 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
:用于生成器函数,暂停函数执行,返回一个值,并保留函数当前状态,下次继续执行。
特性 | return | yield |
---|---|---|
用于的函数类型 | 普通函数 | 生成器函数(generator) |
函数调用后返回 | 立即执行,返回一个值 | 返回一个生成器对象(不执行函数体) |
是否结束函数 | ✅ 结束函数执行 | ❌ 不结束,挂起函数执行 |
返回什么 | 一个值(任意类型) | 每次迭代一个值 |
多次使用时 | 只能返回一次 | 可以多次挂起和恢复,每次返回一个新值 |
执行方式 | 函数一旦调用就完整执行 | 需用 next() 推进一步步执行 |
内部状态保留 | ❌ 不保留,return 后局部变量都销毁 | ✅ 保留局部变量、执行位置 |
适合场景 | 一次性返回一个结果 | 惰性计算、流式处理、大数据、迭代器 |