【yield】如何让python更加优雅

28 阅读6分钟

Python 生成器 在这个教程里,你会学习如何轻松使用python生成器创建迭代,迭代器和常规函数的不同,以及为什么使用它

Python中的生成器 在python中构建迭代器有很大工作我们必须使用__iter__()和__next__()方法实现一个类,追踪内部状态,并在没有值返回时提高StopIteration

这既冗长又违反直觉。生成器在此时就有用了。

Python生成器是创建迭代器的简单方法。偶们上边提到的所有工作其实都是Python的生成器自动处理的。

简而言之,生成器就是一个函数,它返回一个对象(迭代器),我们可以对其进行迭代(一次一个值)

用Python创建生成器 使用Python创建生成器是非常简单的。就像定义普通函数一样简单,但是它使用的是yield语句而不是return.

如果一个函数至少有一个yield语句(他可能包含其它yield语句或return语句),那么它就是生成器函数。yield和return都将从函数返回一些值。

不同之处在于,return语句是完全终止函数,而yield语句只是暂停,之后会继续执行。

生成器函数和普通函数的区别 生成器函数包含一个或多个yield语句 调用时,他返回一个对象(迭代器),但不会立即开始执行 诸如__iter__()和__next__()之类的方法是自动实现的。因此可以遍历所有项目使用next() 一旦执行yield,函数会暂停执行调用的方法 连续调用直接会记住局部变量和他们的状态 最后,函数终止时,在进一步的调用中将自动引发StopIteration 在下面的例子中,我们说明了上边的要点。这是一个叫做my_gen()的生成器函数,其中包含多个yield语句

简单的生成器函数

def my_gen(): n = 1 print("this is printed first") # 生成器函数包含的yield yield n

n += 1
print("this is printed second")
yield n

n += 1
print("this is printed at last")
yield n

运行结果:

#他返回一个对象(迭代器),但不会立即开始执行 a = my_gen()

可以遍历所有项目使用next()

next(a) this is printed first 1

一旦执行yield,函数会暂停执行调用的方法

连续调用直接会记住局部变量和他们的状态

next(a) this is printed second 2 next(a) this is printed at last 3

最后,函数终止时,在进一步的调用中将自动引发StopIteration

next(a)

StopIteration Traceback (most recent call last) in 1 # 最后,函数终止时,在进一步的调用中将自动引发StopIteration ----> 2 next(a)

StopIteration: 我们要记住上边一件非常有趣的事,变量n是在每一哥调用之间是被记住的

一般函数中,局部变量在函数终止时会消失。

生成器对象只能被遍历一次。如果想多遍历一次,必须使用a = my_gen()

也要知道,我们可以直接将生成器函数和for循环一起使用。 这是因为for循环使用一个迭代器,并使用next函数对其迭代。引发StopIteration时,他将自动结束。

A simple generator function

def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n

n += 1
print('This is printed second')
yield n

n += 1
print('This is printed at last')
yield n

Using for loop

for item in my_gen(): print(item) This is printed first 1 This is printed second 2 This is printed at last 3 Python生成器和循环 上边的例子时没有多大实际用途的,我们使用它只是为了演示到底发生了什么

通常,生成器函数通过具备合适终止条件的循环来实现

这是一个用来反转字符串的例子:

def rev_str(myStr): length = len(myStr) for i in range(length-1, -1, -1): yield myStr[i]

反转字符串

for char in rev_str("hello"): print(char) o l l e h 注意:生成器函数不止是可以作用于字符串,而且可以作用与其它可以迭代的类别:列表,元组

Python生成器表达式 简单的生成器可以使用生成器表达式简单的实现。这使得建立生成器非常的简单。

就像lambda表达式可以生成匿名函数,生成器表示式创建匿名的生成器函数

生成器表达式语法很像list comprehension生成,但式方括号变成了圆括号。

list comprehension 和生成器表达式的区别是,list comprehension 是一次创建整个列表,而生成器表达式一次生成一个。所以我们说生成器表达式是懒执行的(只有使用时才生成),这也就导致生成器表达式比list comprehension更加有记忆效率。

初始化列表

myList = [1, 3, 6, 10]

list comprehension

list_ = [x ** 2 for x in myList]

生成器表达式

generator = (x ** 2 for x in myList)

print(list_) print(generator) [1, 9, 36, 100] <generator object at 0x005B2258> 生成器表达式可以当做函数的参数,此时可以省略圆括号

sum(x ** 2 for x in myList) 146 sum((x ** 2 for x in myList)) 146 max(x ** 2 for x in myList) 100 实践 我们有太多原因使用这个强大的功能了

  1. 容易实现 以下的2的幂是一个有利的证明

class PowTwo: def init(self, max=0): self.n = 0 self.max = max

def __iter__(self):
    return self

def __next__(self):
    if self.n > self.max:
        raise StopIteration
        
    result = s ** slef.n
    self.n += 1
    return result

上边的程序很长而且不容易理解,我们这样简化:

def PowtwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 11 2. 内存友好 一个普通的用来返回列表的函数在返回结果之前在内存中会首先创建整个序列。如何序列很大,这是很致命的。

但是生成器是内存有好的,它一次生成一个项目。

  1. 无限数据流 生成器是代表无限数据流的绝佳媒介。无限数据流不能存储在内存中,并且由于生成器一次只生成一项,所以他们可以代表无限数据流。

下面的生成器代表了生成所有偶数:·

def allEven(): n = 0 while True: yield n n += 2 4.流水线生成器 可以使用多个生成器来流水线化一些列操作。比如:

我们有一个生成器产生斐波那契数,另一个生成器用来求平方。我们现在想求斐波那契数列的数的平方的和。我们可以这么做:1

def fibonacci_numbers(nums): x, y = 0, 1 for _ in range(nums): x, y = y, x + y yield x

def square(nums): for num in nums: yield num**2

print(sum(square(fibonacci_numbers(10)))) 4895 a = list(fibonacci_numbers(10)) print(a) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] b = list(square(a)) print(b) [1, 1, 4, 9, 25, 64, 169, 441, 1156, 3025] print(sum(b)) 4895