机票

机票

学习练习

简介

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
Note

生成器迭代器是迭代器的一个特殊子集。 Iterators是让程序能够循环遍历_可迭代对象_的机制或协议。 生成器迭代器和常见的 Python iterables返回的迭代器行为非常相似,但有一些重要的区别需要注意:

  • 它们是_惰性求值_的;迭代是_单向_的,无法“退回”到之前的值。
  • 它们会随着对返回值的迭代而被_消费_;不会重置,也不会保存在内存中。
  • 它们不可排序,也不能反转。
  • 它们不是序列类型,没有indexes。 不能用加法或减法引用前一个或后一个值,也不能使用方括号([])写法或切片。
  • 它们不能用于len()函数,因为没有长度。
  • 它们可以是_有限_的,也可以是_无限_的。从_无限_generator-iterator中收集所有值时要小心!

yield 表达式

yield 表达式和return表达式非常相似。 与return表达式_不同_,yield会在一个_特定的点_把值交给调用方,并暂停对其他值的求值或返回,直到这些值被请求为止。 当yield被求值时,它会暂停所在函数的执行,并返回函数_在那一刻_的值。 之后函数会_保持在作用域中_,而当__next__()被调用时,执行会恢复,直到再次遇到yield。

Note

在函数之外禁止使用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 个航班!

他们目前还在靠人工为乘客分配所有座位,这需要实现自动化。

他们请_你_来编写软件,自动完成乘客座位分配。 他们要求你的软件既节省内存,又性能出色。

1. 生成座位字母

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"

2. 生成座位

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"

3. 为乘客分配座位

现在你已经有了生成座位的函数,可以用它为乘客分配座位了。

实现一个函数 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'}

4. 机票代码

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'
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Python Exercism

准备好开始 机票 了吗?

注册 Exercism,借助 17 个概念146 个练习 和真人导师指导,学习并掌握 Python,全部免费。