生成器是可以在运行中暂停和恢复的函数,返回可以迭代的对象。 与列表不同,它们是懒惰的,因此一次仅在被询问时才产生一项。 因此,在处理大型数据集时,它们的内存效率更高。
生成器的定义类似于普通函数,但是使用 yield
语句而不是 return
。
def my_generator(): yield 1 yield 2 yield 3
|
执行生成器函数
调用该函数不会执行它,而是函数返回一个生成器对象,该对象用于控制执行。 生成器对象在调用 next()
时执行。 首次调用 next()
时,执行从函数的开头开始,一直持续到第一个 yield
语句,在该语句中返回语句右边的值。 随后对 next()
的调用从 yield
语句继续(并循环),直到达到另一个 yield
。 如果由于条件而未调用 yield
或到达末尾,则会引发 StopIteration
异常:
def countdown(num): print('Starting') while num > 0: yield num num -= 1
cd = countdown(3)
print(next(cd))
print(next(cd)) print(next(cd))
print(next(cd))
|
Starting 3 2 1 --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-1-3941498e0bf0> in <module> 16 17 ---> 18 print(next(cd))
StopIteration:
|
cd = countdown(3) for x in cd: print(x)
|
cd = countdown(3) sum_cd = sum(cd) print(sum_cd)
cd = countdown(3) sorted_cd = sorted(cd) print(sorted_cd)
|
Starting 6 Starting [1, 2, 3]
|
最大的优点:迭代器节省内存!
由于这些值是延迟生成的,即仅在需要时才生成,因此可以节省大量内存,尤其是在处理大数据时。 此外,我们不必等到所有元素生成后再开始使用它们。
def firstn(n): num, nums = 0, [] while num < n: nums.append(num) num += 1 return nums
sum_of_first_n = sum(firstn(1000000)) print(sum_of_first_n) import sys print(sys.getsizeof(firstn(1000000)), "bytes")
|
499999500000 8697464 bytes
|
def firstn(n): num = 0 while num < n: yield num num += 1
sum_of_first_n = sum(firstn(1000000)) print(sum_of_first_n) import sys print(sys.getsizeof(firstn(1000000)), "bytes")
|
另一个例子:斐波那契数列
def fibonacci(limit): a, b = 0, 1 while a < limit: yield a a, b = b, a + b
fib = fibonacci(30)
print(list(fib))
|
[0, 1, 1, 2, 3, 5, 8, 13, 21]
|
生成器表达式
就像列表推导一样,生成器可以用相同的语法编写,除了用括号代替方括号。 注意不要混淆它们,因为由于函数调用的开销,生成器表达式通常比列表理解要慢(https://stackoverflow.com/questions/11964130/list-comprehension-vs-generator-expressions-weird-timeit-results/11964478#11964478)。
mygenerator = (i for i in range(1000) if i % 2 == 0) print(sys.getsizeof(mygenerator), "bytes")
mylist = [i for i in range(1000) if i % 2 == 0] print(sys.getsizeof(mylist), "bytes")
|
生成器背后的概念
这个类将生成器实现为可迭代的对象。 它必须实现 __iter__
和 __next__
使其可迭代,跟踪当前状态(在这种情况下为当前数字),并注意 StopIteration
。 它可以用来理解生成器背后的概念。 但是,有很多样板代码,其逻辑并不像使用 yield
关键字的简单函数那样清晰。
class firstn: def __init__(self, n): self.n = n self.num = 0 def __iter__(self): return self def __next__(self): if self.num < self.n: cur = self.num self.num += 1 return cur else: raise StopIteration() firstn_object = firstn(1000000) print(sum(firstn_object))
|
GitHub repo: qiwihui/blog
Follow me: @qiwihui
Site: QIWIHUI