← 学习库 Think Python (2e) · 中英对照 目录

Chapter 12  Tuples 第 12 章 元组

本页译自 Think Python 2e(Allen B. Downey)· Chapter 12 Tuples。代码块保留英文原文不翻译;正文段段对照,中文块可用右下角按钮隐藏。

12.1 Tuples are immutable 12.1 元组不可变

A tuple is a sequence of values. The values can be any type, and they are indexed by integers, so in that respect tuples are a lot like lists. The important difference is that tuples are immutable.

元组是一系列值的序列。这些值可以是任意类型,并用整数索引,因此从这一点看,元组很像列表。重要的区别在于元组是不可变的。

Syntactically, a tuple is a comma-separated list of values:

从语法上看,元组是用逗号分隔的一串值:
>>> t = 'a', 'b', 'c', 'd', 'e'

Although it is not necessary, it is common to enclose tuples in parentheses:

虽然并非必须,但人们通常会把元组用括号括起来:
>>> t = ('a', 'b', 'c', 'd', 'e')

To create a tuple with a single element, you have to include a final comma:

要创建一个只含一个元素的元组,必须在末尾加上一个逗号:
>>> t1 = 'a',
>>> type(t1)
<type 'tuple'>

A value in parentheses is not a tuple:

带括号的单个值并不是元组:
>>> t2 = ('a')
>>> type(t2)
<type 'str'>

Another way to create a tuple is the built-in function tuple. With no argument, it creates an empty tuple:

另一种创建元组的方式是使用内置函数 tuple。不带参数时,它会创建一个空元组:
tuple0010

If the argument is a sequence (string, list or tuple), the result is a tuple with the elements of the sequence:

如果参数是一个序列(字符串、列表或元组),结果就是一个包含该序列各元素的元组:
tuple0011

Because tuple is the name of a built-in function, you should avoid using it as a variable name.

因为 tuple 是内置函数的名字,你应该避免把它用作变量名。

Most list operators also work on tuples. The bracket operator indexes an element:

列表上的大部分运算符在元组上也适用。方括号运算符用来索引某个元素:
tuple0014

And the slice operator selects a range of elements.

切片运算符用来选取一段元素。
tuple0015

But if you try to modify one of the elements of the tuple, you get an error:

但如果你试图修改元组里的某个元素,就会报错:
tuple0016

You can’t modify the elements of a tuple, but you can replace one tuple with another:

你不能修改元组里的元素,但可以把一个元组整体替换成另一个:
tuple0017

12.2 Tuple assignment 12.2 元组赋值

It is often useful to swap the values of two variables. With conventional assignments, you have to use a temporary variable. For example, to swap a and a:

交换两个变量的值常常很有用。用传统的赋值方式,你必须借助一个临时变量。例如,要交换 aa
tuple0022

This solution is cumbersome; tuple assignment is more elegant:

这种做法比较笨拙;元组赋值更优雅:
tuple0023

The left side is a tuple of variables; the right side is a tuple of expressions. Each value is assigned to its respective variable. All the expressions on the right side are evaluated before any of the assignments.

左边是一组变量的元组,右边是一组表达式的元组。每个值被赋给对应的变量。右边所有表达式都会在任何赋值发生之前先被求值。

The number of variables on the left and the number of values on the right have to be the same:

左边变量的个数和右边值的个数必须相同:
tuple0024

More generally, the right side can be any kind of sequence (string, list or tuple). For example, to split an email address into a user name and a domain, you could write:

更一般地,右边可以是任意一种序列(字符串、列表或元组)。例如,要把一个电子邮件地址拆成用户名和域名,可以这样写:
tuple0025

The return value from tuple is a list with two elements; the first element is assigned to tuple, the second to tuple0.

tuple 的返回值是一个包含两个元素的列表:第一个元素赋给 tuple,第二个赋给 tuple0。
tuple0032

12.3 Tuples as return values 12.3 元组作为返回值

Strictly speaking, a function can only return one value, but if the value is a tuple, the effect is the same as returning multiple values. For example, if you want to divide two integers and compute the quotient and remainder, it is inefficient to compute a00 and then a00. It is better to compute them both at the same time.

严格来说,一个函数只能返回一个值,但如果返回的是一个元组,效果就等于返回了多个值。例如,要把两个整数相除并算出商和余数,先算 a00 再算 a00 效率很低,最好同时算出来。

The built-in function tuple0 takes two arguments and returns a tuple of two values, the quotient and remainder. You can store the result as a tuple:

内置函数 tuple0 接受两个参数,返回一个包含两个值的元组,即商和余数。你可以把结果存成一个元组:
tuple0039

Or use tuple assignment to store the elements separately:

也可以用元组赋值把两个元素分别存起来:
tuple0040

Here is an example of a function that returns a tuple:

下面这个函数返回一个元组:
tuple0041

a00 and a00 are built-in functions that find the largest and smallest elements of a sequence. tuple00 computes both and returns a tuple of two values.

a00 和 a00 是找出序列中最大、最小元素的内置函数。tuple00 同时算出两者,返回一个包含两个值的元组。

12.4 Variable-length argument tuples 12.4 可变长实参元组

Functions can take a variable number of arguments. A parameter name that begins with a gathers arguments into a tuple. For example, tuple004 takes any number of arguments and prints them:

函数可以接受数量不定的实参。以 a 开头的形参会把实参打包成一个元组。例如,tuple005 接受任意数量的实参并把它们打印出来:
tuple0052

The gather parameter can have any name you like, but a000 is conventional. Here’s how the function works:

打包用的形参可以随便起名,但习惯上用 a000。下面是这个函数的运行效果:
tuple0055

The complement of gather is scatter. If you have a sequence of values and you want to pass it to a function as multiple arguments, you can use the a operator. For example, tuple0 takes exactly two arguments; it doesn’t work with a tuple:

打包的反操作是拆包。如果你有一串值,想把整串作为多个实参传给函数,可以用 a 运算符。例如,tuple0 正好接受两个实参,不能直接给它一个元组:
tuple0060

But if you scatter the tuple, it works:

但如果你把元组拆包,它就能正常工作:
tuple0061

Exercise 1

习题 1

Many of the built-in functions use variable-length argument tuples. For example, a00 and a00 can take any number of arguments:

很多内置函数都用了可变长实参元组。例如,a00 和 a00 可以接受任意数量的实参:
tuple0066

But a00 does not.

a00 不行。
tuple0069

Write a function called tuple0 that takes any number of arguments and returns their sum.

写一个名为 tuple0 的函数,接受任意数量的实参并返回它们的和。

12.5 Lists and tuples 12.5 列表与元组

a00 is a built-in function that takes two or more sequences and “zips” them into a list of tuples where each tuple contains one element from each sequence. In Python 3, a00 returns an iterator of tuples, but for most purposes, an iterator behaves like a list.

a00 是一个内置函数,它接受两个或更多序列,把它们「拉链」式拼成元组的列表,每个元组包含各序列中的一个元素。在 Python 3 中,a00 返回的是元组的迭代器,但在大多数情况下,迭代器和列表表现一致。

This example zips a string and a list:

下面这个例子把一个字符串和一个列表拉链在一起:
tuple0076

The result is a list of tuples where each tuple contains a character from the string and the corresponding element from the list.

结果是一个元组列表,每个元组包含字符串中的一个字符以及列表中相应的元素。

If the sequences are not the same length, the result has the length of the shorter one.

如果各序列长度不同,结果的长度取较短者的长度。
tuple0077

You can use tuple assignment in a a00 loop to traverse a list of tuples:

你可以在 a00 循环里用元组赋值来遍历元组列表:
tuple0080

Each time through the loop, Python selects the next tuple in the list and assigns the elements to tuple0 and tuple0. The output of this loop is:

每轮循环,Python 取列表里的下一个元组,把元素赋给 tuple0 和 tuple0。这个循环的输出是:
tuple0085

If you combine a00, a00 and tuple assignment, you get a useful idiom for traversing two (or more) sequences at the same time. For example, tuple0088 takes two sequences, a0 and a0, and returns a000 if there is an index a such that tuple0093:

如果把 a00、a00 和元组赋值组合起来,就得到一种同时遍历两个(或更多)序列的常用写法。例如,tuple0096 接受两个序列 a0 和 a0,只要存在某个下标 a 使得 tuple0100,就返回 a000:
tuple0102

If you need to traverse the elements of a sequence and their indices, you can use the built-in function tuple0103:

如果你需要同时遍历序列的元素及其下标,可以用内置函数 tuple0104:
tuple0105

The output of this loop is:

这个循环的输出是:
tuple0106

Again.

又是这样。

12.6 Dictionaries and tuples 12.6 字典与元组

Dictionaries have a method called tuple that returns a list of tuples, where each tuple is a key-value pair.

字典有一个叫 tuple 的方法,它返回一个元组列表,每个元组是一个键值对。
tuple0109

As you should expect from a dictionary, the items are in no particular order. In Python 3, tuple returns an iterator, but for many purposes, iterators behave like lists.

正如你对字典所预期的,这些项没有特定顺序。在 Python 3 中,tuple 返回的是一个迭代器,但在很多情况下,迭代器表现和列表一样。

Going in the other direction, you can use a list of tuples to initialize a new dictionary:

反过来,你也可以用元组列表来初始化一个新字典:
tuple0112

Combining a000 with a00 yields a concise way to create a dictionary:

a000 和 a00 组合起来,就得到一种简洁的建字典方式:
tuple0117

The dictionary method tuple0 also takes a list of tuples and adds them, as key-value pairs, to an existing dictionary.

字典的 tuple0 方法也接受元组列表,把它们作为键值对添加到已有的字典中。

Combining tuple, tuple assignment and a00, you get the idiom for traversing the keys and values of a dictionary:

tuple、元组赋值和 a00 组合起来,就得到遍历字典键和值的常用写法:
tuple0124

The output of this loop is:

这个循环的输出是:
tuple0125

Again.

又是这样。

It is common to use tuples as keys in dictionaries (primarily because you can’t use lists). For example, a telephone directory might map from last-name, first-name pairs to telephone numbers. Assuming that we have defined a000, tuple and tuple0, we could write:

用元组作字典的键很常见(主要是因为你不能用列表作键)。例如,电话簿可能把「姓、名」这种组合映射到电话号码。假设我们已经定义了 a000、tupletuple0,可以这样写:
tuple0132

The expression in brackets is a tuple. We could use tuple assignment to traverse this dictionary.

方括号里的表达式是一个元组。我们可以用元组赋值来遍历这个字典。
tuple0133

This loop traverses the keys in tuple0134, which are tuples. It assigns the elements of each tuple to a000 and tuple, then prints the name and corresponding telephone number.

这个循环遍历 tuple0137 里的键,那些键都是元组。它把每个元组的元素赋给 a000 和 tuple,然后打印姓名和对应的电话号码。

Figure 12.1: State diagram.

图 12.1:状态图(原书插图未收录)

But in a larger diagram you might want to leave out the details. For example, a diagram of the telephone directory might appear as in Figure 12.2.

不过在更大的图里,你可能想省掉这些细节。例如,电话簿的图可能画成图 12.2 那样。

Figure 12.2: State diagram.

图 12.2:状态图(原书插图未收录)

Here the tuples are shown using Python syntax as a graphical shorthand.

这里用 Python 语法来表示元组,作为一种图形简写。

The telephone number in the diagram is the complaints line for the BBC, so please don’t call it.

图中那个电话号码是 BBC 的投诉热线,所以请不要拨打。

12.7 Comparing tuples 12.7 比较元组

The relational operators work with tuples and other sequences; Python starts by comparing the first element from each sequence. If they are equal, it goes on to the next elements, and so on, until it finds elements that differ. Subsequent elements are not considered (even if they are really big).

关系运算符对元组和别的序列同样适用;Python 先比较每个序列的第一个元素。如果相等,就继续比较下一个,依此类推,直到找到不同的元素。之后的元素不再考虑(哪怕它们非常大)。
tuple0140

The a000 function works the same way. It sorts primarily by first element, but in the case of a tie, it sorts by second element, and so on.

a000 函数也是同样的道理。它主要按第一个元素排序,遇到并列时再按第二个元素排序,依此类推。

This feature lends itself to a pattern called DSU for

这个特性催生出一种被称为 DSU 模式(装饰-排序-还原 decorate-sort-undecorate)的套路,用于:
Decorate
a sequence by building a list of tuples with one or more sort keys preceding the elements from the sequence,
Sort
the list of tuples, and
Undecorate
by extracting the sorted elements of the sequence.
Decorate 装饰
先构造一个元组列表来包装序列:在每个元素前面加上一个或多个排序键,
Sort 排序
对这个元组列表排序,
Undecorate 还原
再从中取出排好序的元素。

For example, suppose you have a list of words and you want to sort them from longest to shortest:

例如,假设你有一个单词列表,想按从长到短排序:
tuple0143

The first loop builds a list of tuples, where each tuple is a word preceded by its length.

第一个循环构造一个元组列表,每个元组里单词排在其长度之后。

a000 compares the first element, length, first, and only considers the second element to break ties. The keyword argument tuple0145 tells a000 to go in decreasing order.

a000 先比较第一个元素(即长度),只有当长度并列时才看第二个元素。关键字参数 tuple0148 让 a000 按降序排列。

The second loop traverses the list of tuples and builds a list of words in descending order of length.

第二个循环遍历这个元组列表,按长度递减的顺序构造出一个单词列表。

Exercise 2

习题 2

In this example, ties are broken by comparing words, so words with the same length appear in reverse alphabetical order. For other applications you might want to break ties at random. Modify this example so that words with the same length appear in random order. Hint: see the tuple0 function in the tuple0 module. Solution: tuple0152.

在这个例子中,并列时靠比较单词来打破,所以长度相同的单词会按字母逆序出现。在别的应用里,你可能想随机地打破并列。修改这个例子,让长度相同的单词以随机顺序出现。提示:参见 tuple0 模块里的 tuple0 函数。答案见 tuple0155。

12.8 Sequences of sequences 12.8 序列的序列

I have focused on lists of tuples, but almost all of the examples in this chapter also work with lists of lists, tuples of tuples, and tuples of lists. To avoid enumerating the possible combinations, it is sometimes easier to talk about sequences of sequences.

我一直在讲元组的列表,但本章里几乎所有例子对列表的列表、元组的元组、以及元组的列表也同样成立。为了避免把各种组合一一列举,有时干脆把它们统称为「序列的序列」更省事。

In many contexts, the different kinds of sequences (strings, lists and tuples) can be used interchangeably. So how and why do you choose one over the others?

在很多场合,不同类型的序列(字符串、列表、元组)可以互换使用。那么到底该选哪种、又是为什么?

To start with the obvious, strings are more limited than other sequences because the elements have to be characters. They are also immutable. If you need the ability to change the characters in a string (as opposed to creating a new string), you might want to use a list of characters instead.

最明显的一点:字符串比其他序列受限更多,因为它的元素必须是字符。而且字符串也不可变。如果你需要修改字符串里的字符(而不是新建一个字符串),那就改用字符列表吧。

Lists are more common than tuples, mostly because they are mutable. But there are a few cases where you might prefer tuples:

列表比元组更常用,主要因为它可变。但有几种情况你可能会更想用元组:
  1. In some contexts, like a tuple0 statement, it is syntactically simpler to create a tuple than a list. In other contexts, you might prefer a list.
  2. If you want to use a sequence as a dictionary key, you have to use an immutable type like a tuple or string.
  3. If you are passing a sequence as an argument to a function, using tuples reduces the potential for unexpected behavior due to aliasing.
  1. 有些场合(比如 tuple0 语句)里,构造一个元组比构造列表在语法上更简单。别的场合你或许更想要列表。
  2. 如果你想把一个序列用作字典的键,就必须用元组或字符串这样的不可变类型。
  3. 如果把序列作为实参传给函数,用元组能减少因别名问题引发意外行为的机会。

Because tuples are immutable, they don’t provide methods like a000 and tuple01, which modify existing lists. But Python provides the built-in functions tuple0 and tuple016, which take any sequence as a parameter and return a new list with the same elements in a different order.

因为元组不可变,它没有提供像 a000 和 tuple01 这种会修改已有列表的方法。但 Python 提供了内置函数 tuple0 和 tuple016,它们接受任意序列作参数,返回元素顺序不同、内容相同的新列表。

12.9 Debugging 12.9 调试

Lists, dictionaries and tuples are known generically as data structures; in this chapter we are starting to see compound data structures, like lists of tuples, and dictionaries that contain tuples as keys and lists as values. Compound data structures are useful, but they are prone to what I call shape errors; that is, errors caused when a data structure has the wrong type, size or composition. For example, if you are expecting a list with one integer and I give you a plain old integer (not in a list), it won’t work.

列表、字典和元组被统称为数据结构;本章我们开始见到复合数据结构,比如元组的列表、以及以元组为键、列表为值的字典。复合数据结构很有用,但也容易出我所谓的形状错误——也就是数据结构的类型、大小或构成不对时引发的错误。例如,如果你期望得到一个含一个整数的列表,而我给的是一个孤零零的整数(不在列表里),那就行不通。

To help debug these kinds of errors, I have written a module called tuple0166 that provides a function, also called tuple0167, that takes any kind of data structure as an argument and returns a string that summarizes its shape. You can download it from tuple0168

为了帮忙调试这类错误,我写了一个叫 tuple0169 的模块,它提供一个同名函数,接受任意类型的数据结构作参数,返回一个概括其形状的字符串。你可以从 tuple0170 下载。

Here’s the result for a simple list:

下面是一个简单列表的结果:
tuple0171

A fancier program might write “list of 3 ints,” but it was easier not to deal with plurals. Here’s a list of lists:

更讲究的程序也许会写成「list of 3 ints」,但省掉复数变化更简单。下面是一个列表的列表:
tuple0172

If the elements of the list are not the same type, tuple0173 groups them, in order, by type:

如果列表里的元素类型不统一,tuple0174 会按类型依次分组:
tuple0175

Here’s a list of tuples:

下面是一个元组的列表:
tuple0176

And here’s a dictionary with 3 items that map integers to strings.

下面是一个含 3 项、把整数映射到字符串的字典:
tuple0177

If you are having trouble keeping track of your data structures, tuple0178 can help.

如果你难以理清自己的数据结构,tuple0179 能帮上忙。

12.10 Glossary 12.10 术语表

tuple:
An immutable sequence of elements.
tuple assignment:
An assignment with a sequence on the right side and a tuple of variables on the left. The right side is evaluated and then its elements are assigned to the variables on the left.
gather:
The operation of assembling a variable-length argument tuple.
scatter:
The operation of treating a sequence as a list of arguments.
DSU:
Abbreviation of “decorate-sort-undecorate,” a pattern that involves building a list of tuples, sorting, and extracting part of the result.
data structure:
A collection of related values, often organized in lists, dictionaries, tuples, etc.
shape (of a data structure):
A summary of the type, size and composition of a data structure.
tuple 元组:
不可变的元素序列。
tuple assignment 元组赋值:
一种赋值:右边是一个序列,左边是一组变量。先对右边求值,再将其元素赋给左边的变量。
gather 打包:
把可变长实参组装成元组(可变长实参元组)的操作。
scatter 拆包:
把序列当作实参列表来使用的操作。
DSU 模式:
「decorate-sort-undecorate(装饰-排序-还原)」的缩写,指先构造元组列表、排序、再取出部分结果的一种套路。
data structure 数据结构:
一组相关值的集合,常以列表、字典、元组等形式组织。
shape(数据结构的形状):
对数据结构类型、大小与构成的概括描述。

12.11 Exercises 12.11 习题

Exercise 3

习题 3

Write a function called tuple0180 that takes a string and prints the letters in decreasing order of frequency. Find text samples from several different languages and see how letter frequency varies between languages. Compare your results with the tables at tuple0181. Solution: tuple0182.

写一个名为 tuple0183 的函数,接受一个字符串,按字母出现频率从高到低打印。找几种不同语言的文本样本,看看字母频率因语言而异的情况。把你的结果和 tuple0184 的表格对照。答案见 tuple0185。

Exercise 4

习题 4

More anagrams!

更多变位词!
  1. Write a program that reads a word list from a file (see Section 9.1) and prints all the sets of words that are anagrams. Here is an example of what the output might look like: tuple0186 Hint: you might want to build a dictionary that maps from a set of letters to a list of words that can be spelled with those letters. The question is, how can you represent the set of letters in a way that can be used as a key?
  2. Modify the previous program so that it prints the largest set of anagrams first, followed by the second largest set, and so on.
  3. In Scrabble a “bingo” is when you play all seven tiles in your rack, along with a letter on the board, to form an eight-letter word. What set of 8 letters forms the most possible bingos? Hint: there are seven. Solution: tuple0187.
  1. 写一个程序,从文件里读入单词列表(见第 9.1 节),打印出所有互为变位词的单词集合。 下面是一种可能的输出样子:
    (上方代码块为英文输出示例,不译。)
    提示:你可以建一个字典,把「一组字母」映射到能用这些字母拼出的单词列表。问题在于,怎样表示这组字母才能用作键?
  2. 修改上面的程序,让它先打印最大的变位词集合,再打印第二大的,依此类推。
  3. 在 Scrabble 拼字游戏里,「bingo」指你用手上七块牌加上棋盘上的一个字母,拼出一个八个字母的单词。哪八字母组合能拼出的 bingo 最多?提示:共有七种。 答案见 tuple0188。

Exercise 5

习题 5

Two words form a “metathesis pair” if you can transform one into the other by swapping two letters; for example, “converse” and “conserve.” Write a program that finds all of the metathesis pairs in the dictionary. Hint: don’t test all pairs of words, and don’t test all possible swaps. Solution: tuple0189. Credit: This exercise is inspired by an example at tuple0190.

如果通过交换两个字母就能把一个词变成另一个,这两个词就构成一个「metathesis pair(换位对)」,比如 converse 和 conserve。写一个程序,找出字典里所有的换位对。提示:不要穷举所有词对,也不要穷举所有可行的交换。答案见 tuple0191。致谢:本题灵感来自 tuple0192 的一个例子。

Exercise 6

习题 6

Here’s another Car Talk Puzzler (tuple0193):

再来一个 Car Talk 谜题(tuple0194):

What is the longest English word, that remains a valid English word, as you remove its letters one at a time?

哪个最长的英文单词,在每次去掉一个字母之后,依然是一个合法的英文单词?

Now, letters can be removed from either end, or the middle, but you can’t rearrange any of the letters. Every time you drop a letter, you wind up with another English word. If you do that, you’re eventually going to wind up with one letter and that too is going to be an English word—one that’s found in the dictionary. I want to know what’s the longest word and how many letters does it have?

字母可以从两端或中间去掉,但你不能重新排列任何字母。每去掉一个字母,就得到另一个英文单词。这样做下去,最终会只剩一个字母,而那个字母本身也得是个字典里能查到的英文单词。我想知道最长的词是哪个,它有几个字母?

I’m going to give you a little modest example: Sprite. Ok? You start off with sprite, you take a letter off, one from the interior of the word, take the r away, and we’re left with the word spite, then we take the e off the end, we’re left with spit, we take the s off, we’re left with pit, it, and I.

我给你一个小例子:Sprite。从 sprite 开始,去掉一个内部的字母 r,剩下 spite;再从末尾去掉 e,剩下 spit;去掉 s,剩下 pit、it、I。

Write a program to find all words that can be reduced in this way, and then find the longest one.

写一个程序,找出所有能这样逐步缩减的单词,然后找出最长的那个。

This exercise is a little more challenging than most, so here are some suggestions:

这题比一般的略难,下面是一些建议:
  1. You might want to write a function that takes a word and computes a list of all the words that can be formed by removing one letter. These are the “children” of the word.
  2. Recursively, a word is reducible if any of its children are reducible. As a base case, you can consider the empty string reducible.
  3. The wordlist I provided, tuple0195, doesn’t contain single letter words. So you might want to add “I”, “a”, and the empty string.
  4. To improve the performance of your program, you might want to memoize the words that are known to be reducible.
  1. 你可以写一个函数,接受一个单词,算出去掉一个字母后能得到的所有单词。这些就是该词的「子词」。
  2. 递归地看,只要某个词的任一子词可缩减,这个词就可缩减。基础情形可以把空串视为可缩减。
  3. 我提供的词表 tuple0196 不含单字母单词,所以你可能要补上 I、a 和空串。
  4. 为了提升程序性能,你可以把已知可缩减的单词做记忆化(memoize)缓存。

Solution: tuple0197.

答案见 tuple0198。