generator是一种函数或表达式,它返回一种特殊的迭代器,叫作generator iterator。
Generator-iterator是惰性的:它们不把values存在内存里,而是在需要时才_生成_这些值。
生成器函数看起来和其他函数一样,但它包含一个或多个 yield 表达式。
每个yield都会暂停代码执行,保存当前的执行状态(包括所有局部变量和 try 语句)。
当生成器恢复执行时,它会从暂停的地方接着执行,而不像普通函数那样每次调用都会重置。
生成器的构造方式和其他循环或递归函数很像,但需要一个 yield表达式,稍后我们会详细探讨。
一个例子是返回给定数字数组中各项_平方_的函数。 按现在的写法,必须先处理完所有输入,才能返回任何值:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
可以像这样把这个函数改写成生成器:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
这样做的道理是:当你不需要_一次性_产出所有值时,就可以使用生成器。 这能节省内存和运算能力,因为只计算你_当前正在处理_的那个值。
在 Python 中,生成器可以替代大多数iterables。
这包括那些需要一个iterable/iterator作为参数的_函数_或_对象_。
要使用squares_generator()生成器:
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
generator中的值也可以通过next()函数来产出或访问。
next()会调用 generator-iterator 对象的__next__()方法,“推进”或求值代码直到它的yield表达式,然后该表达式会“产出”或返回一个值:
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
当generator-iterator被完全消费、没有更多值可返回时,它会抛出StopIteration错误。
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
生成器迭代器是迭代器的一个特殊子集。
Iterators是让程序能够循环遍历_可迭代对象_的机制或协议。
生成器迭代器和常见的 Python iterables返回的迭代器行为非常相似,但有一些重要的区别需要注意:
indexes。
不能用加法或减法引用前一个或后一个值,也不能使用方括号([])写法或切片。len()函数,因为没有长度。generator-iterator中收集所有值时要小心!yield 表达式和return表达式非常相似。
与return表达式_不同_,yield会在一个_特定的点_把值交给调用方,并暂停对其他值的求值或返回,直到这些值被请求为止。
当yield被求值时,它会暂停所在函数的执行,并返回函数_在那一刻_的值。
之后函数会_保持在作用域中_,而当__next__()被调用时,执行会恢复,直到再次遇到yield。
在函数之外禁止使用yield表达式。
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
生成器在很多场景中都很有用。
当处理可能很大的数据集合时,你可能不想把它们全部放进内存。 生成器可以逐块处理较大的数据,既节省内存,又能提升性能。
当某个过程或计算_复杂_、_昂贵_或_无限_时,生成器也很有帮助:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
现在,每当对infinite_sequence对象调用__next__()时,它都会返回_上一个数字_ + 1。
Conda Airlines 是编程界最大的航空公司,每天有超过 10,000 个航班!
他们目前还在靠人工为乘客分配所有座位,这需要实现自动化。
他们请_你_来编写软件,自动完成乘客座位分配。 他们要求你的软件既节省内存,又性能出色。
Conda 想为他们的飞机生成座位字母。
一架飞机由一排排座位组成。
每排都有 4 个座位。
每排的座位总是命名为 A、B、C 和 D。
一排中的第一个座位是 A,第二个座位是 B,依此类推。
到达 D 之后,再重新从 A 开始。
实现一个函数 generate_seat_letters(<number>),它接受一个 int,表示要生成多少个座位字母。
该函数应返回一个座位字母的_可迭代对象_。
>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"
Conda 想要一个系统,能为他们的飞机生成指定数量的座位。
每架飞机的每一排都有 4 个座位。
排号用数字表示,从 1 开始递增。
座位应按这样的顺序排列:1A、1B、1C、1D、2A、2B、2C、2D、3A、3B、3C、3D、……
下面是一个例子:
| x | 1 | 2 |
|---|---|---|
| 排 | 5 | 21 |
| 座位字母 | A | D |
| 结果 | 5A | 21D |
由于乘客的迷信,很多航空公司的航班都没有第 13 排。 Conda Airlines 也遵循这一惯例,所以请务必_不要_生成第 13 排的座位。
实现一个函数 generate_seats(<number>),它接受一个 int,表示要生成多少个座位。
该函数应返回一个包含所生成座位的_可迭代对象_。
>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"
现在你已经有了生成座位的函数,可以用它为乘客分配座位了。
实现一个函数 assign_seats(<passengers>),它接受一个由乘客姓名组成的 list。
该函数应返回一个_字典_,以 passenger 为_键_,以 seat_number 为_值_。
>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']
>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
Conda Airlines 希望每张机票都有一个唯一的代码。 由于他们是一家大型航空公司,航班非常多。 这意味着会有多个航班的座位号相同。 他们希望你创建一个系统,生成一个长度为 12 个字符的唯一机票字符串。
该代码以 assigned_seat 开头,后面跟 flight_id。
代码的其余部分用 0s 填充。
实现一个函数 generate_codes(<seat_numbers>, <flight_id>),它接受一个由 seat_numbers 组成的 list 和一个包含航班号的 string。
该函数应返回一个 generator,它会产出 ticket_number。
>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)
>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'