一篇文章教你如何使用Python生成器
自从PEP 自从255引入生成器以来,它就是 Python 重要组成部分.
生成器允许您定义具有迭代行为的函数.
它使程序猿更快、更简单、更干净地创建迭代器.
那么什么是迭代器,你可能会问?
iterator迭代器是一个可以迭代的(循环)对象。它可以抽象成一个具有数据和可迭代对象行为的容器。也许你每天都在使用一些可迭代对象:如字符串、列表、字典或其他名称.
迭代器是实现迭代器接口Iterator Protocol类。该接口为类别提供了两种方法:__iter__和__next__.
嗯~回到最后一步。为什么要创建迭代器?
节省内存空间
例化后,迭代器不会计算每个项目的值,它们只会在您访问这些项目时进行计算。这就是众所周知的惰性求值。
当你有一个非常大的数据集需要计算时,惰性求值是非常有用的。尽管整个数据集仍在计算中,但它允许您立即开始使用数据。
假设我们想要获得比某个值小的所有素数。
我们先定义一个函数,它可以检查一个数字是否为素数:
defcheck_prime(number): forpisorinrange(2,int(number**0.5)+1): ifnumber%pisor==0: returnFalse returnTrue
然后,我们定义了包括___在内的迭代器类iter__和__next__方法。
classPrimes: def__init__(self,max): self.max=max self.number=1 def__iter__(self): returnself def__next__(self): self.number+=1 ifself.number>=self.max: raiseStopIteration elifcheck_prime(self.number): returnself.number else: returnself.__next__()
Primes类通过给定一个值来实例化。如果下一个素数比max更大,迭代器会抛出一个Stopiteration异常来停止迭代器。
当我们要求迭代器中的下一个元素时,它会给number添加。 1 并检查这个数字是否为素数。如果没有,它将再次呼叫__next在number成为素数之前,__。一旦发生这种情况,迭代器就会返回这个数字。
通过使用迭代器,我们不会在内存中创建一个包含大量素数的列表。相反,我们将在每次要求下一个素数时生成它。
让我们试一试:
primes=Primes(100000000000) print(primes) forxinprimes: print(x) ... <__main__.Primesobjectat0x1834a> 2 3 5 7 11 ...
Primes对象的每一次迭代都被调用__next__生成下一个素数。
迭代器只能迭代一轮。如果你试图再次迭代primes,它将不会返回任何值,表现得像一个空列表。
现在我们已经知道了什么是迭代器,以及如何制造一个迭代器,我们将在下一步继续看生成器。
生成器
回想起来,生成器函数允许我们以更简单的方式创建迭代器。
生成器给 Python 引入yield声明。它有点像return,因为它会返回一个值。
不同的是,yield将保存函数的状态。当函数下次被调用时,它将继续从它离开的地方执行,变量值与之前执行yield操作之前相同。
若将我们的Primes迭代器转换为生成器,它看起来就像这样:
defPrimes(max): number=1 whilenumber<max: number+=1 ifcheck_prime(number): yieldnumber primes=Primes(100000000000) print(primes) forxinprimes: print(x) ... <primesat0x10214de08,generatorobject> 2 3 5 7 11
现在真是太 pythonic 我们还能再给力点吗?
当然我们可以用PEP 289中介绍的生成器表达式。
这相当于生成器的列表推导式。它使用与列表推导式相同,但表达式由()包裹而不是[]。
以下表达式可以取代上述生成器函数:
primes=(iforiinrange(2,100000000000)ifcheck_prime(i)) print(primes) forxinprimes: print(x) ... <generatorobject<genexpr>at0x101868e08> 2 3 5 7 11 ...
这就是 Python 生成器的美。
总结
生成器允许你使用一个非常非常好的 pythonic 创建迭代器的方法。迭代器允许惰性求值,只有在要求下一个元素时,迭代器对象才会生成。这对大型数据集非常有用。迭代器和生成器只能迭代一轮。生成器函数优于迭代器。生成器表达式优于迭代器(仅在简单情况下)。