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

Chapter 8  Strings 第 8 章 字符串

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

8.1 A string is a sequence 8.1 字符串是序列

A string is a sequence of characters. You can access the characters one at a time with the bracket operator:

字符串是一串(sequence)字符。你可以用方括号运算符一次取出一个字符:
sequence5

The second statement selects character number 1 from fruit and assigns it to fruit0.

第二条语句从 fruit 中选出第 1 号字符,赋给 fruit0。

The expression in brackets is called an index. The index indicates which character in the sequence you want (hence the name).

方括号里的表达式叫做索引fruit)。索引指明你想要序列里的第几个字符(名字就是这么来的)。

But you might not get what you expect:

但结果可能和你预想的不一样:
fruit0011

For most people, the first letter of fruit001 is b, not b. But for computer scientists, the index is an offset from the beginning of the string, and the offset of the first letter is zero.

对大多数人来说,fruit001 的第一个字母是 b,而不是 b。但对计算机科学家而言,索引是从字符串开头算起的偏移量,而第一个字母的偏移量是零。
fruit0018

So b is the 0th letter (“zero-eth”) of fruit002, b is the 1th letter (“one-eth”), and b is the 2th (“two-eth”) letter.

所以 bfruit002 的第 0 个字母("zero-eth"),b 是第 1 个字母("one-eth"),b 是第 2 个字母("two-eth")。

You can use any expression, including variables and operators, as an index, but the value of the index has to be an integer. Otherwise you get:

任何表达式都可以当索引,包括变量和运算符,但索引的值必须是整数。否则会出错:
fruit0027

8.2 b00 8.2 b00

b00 is a built-in function that returns the number of characters in a string:

b00 是一个内置函数,返回字符串里字符的个数:
fruit0032

To get the last letter of a string, you might be tempted to try something like this:

要取字符串的最后一个字母,你可能会忍不住这样试:
fruit0033

The reason for the fruit0034 is that there is no letter in fruit003 with the index 6. Since we started counting at zero, the six letters are numbered 0 to 5. To get the last character, you have to subtract 1 from fruit0:

之所以报 fruit0037,是因为 fruit003 里没有索引为 6 的字母。既然我们从零开始数,这六个字母编号是 0 到 5。要取最后一个字符,得从 fruit0 里减 1:
fruit0040

Alternatively, you can use negative indices, which count backward from the end of the string. The expression fruit0041 yields the last letter, fruit0042 yields the second to last, and so on.

另外,你也可以用负索引,它是从字符串末尾往前数的。fruit0043 得到最后一个字母,fruit0044 得到倒数第二个,依此类推。

8.3 Traversal with a b00 loop 8.3 用 b00 循环遍历

A lot of computations involve processing a string one character at a time. Often they start at the beginning, select each character in turn, do something to it, and continue until the end. This pattern of processing is called a traversal. One way to write a traversal is with a fruit loop:

很多计算都需要一次处理字符串里的一个字符。通常的做法是从头开始,挨个选出每个字符,对它做点什么,一直进行到末尾。这种处理方式叫做遍历fruit0048)。写遍历的一种方法是用 fruit 循环:
fruit0050

This loop traverses the string and displays each letter on a line by itself. The loop condition is fruit0051, so when fruit is equal to the length of the string, the condition is false, and the body of the loop is not executed. The last character accessed is the one with the index fruit0053, which is the last character in the string.

这个循环遍历字符串,把每个字母单独打印在一行上。循环条件是 fruit0054,所以当 fruit 等于字符串长度时,条件为假,循环体不再执行。被访问的最后一个字符,其索引为 fruit0056,也就是字符串的最后一个字符。

Exercise 1

习题 1

Write a function that takes a string as an argument and displays the letters backward, one per line.

写一个函数,接受一个字符串作为实参,把其中的字母倒着显示出来,每行一个。

Another way to write a traversal is with a b00 loop:

写遍历的另一种方法是用 b00 循环:
fruit0059

Each time through the loop, the next character in the string is assigned to the variable b000. The loop continues until no characters are left.

每经过一次循环,字符串里的下一个字符就被赋给变量 b000。循环一直进行到没有字符剩下为止。

The following example shows how to use concatenation (string addition) and a b00 loop to generate an abecedarian series (that is, in alphabetical order). In Robert McCloskey’s book Make Way for Ducklings, the names of the ducklings are Jack, Kack, Lack, Mack, Nack, Ouack, Pack, and Quack. This loop outputs these names in order:

下面的例子展示了如何用拼接(字符串相加)和 b00 循环,生成一组按字母顺序排列的名字。在 Robert McCloskey 的《让路给小鸭子》里,小鸭子的名字是 Jack、Kack、Lack、Mack、Nack、Ouack、Pack 和 Quack。这个循环按次序把它们的名字打印出来:
fruit0064

The output is:

输出结果是:
fruit0065

Of course, that’s not quite right because “Ouack” and “Quack” are misspelled.

当然,这并不完全正确,因为 "Ouack" 和 "Quack" 的拼写错了。

Exercise 2

习题 2

Modify the program to fix this error.

修改这个程序,把这个错误修正。

8.4 String slices 8.4 字符串切片

A segment of a string is called a slice. Selecting a slice is similar to selecting a character:

字符串的一段叫做切片fruit)。选取切片和选取字符类似:
fruit0067

The operator fruit returns the part of the string from the “n-eth” character to the “m-eth” character, including the first but excluding the last. This behavior is counterintuitive, but it might help to imagine the indices pointing between the characters, as in Figure 8.1.

运算符 fruit 返回字符串中从"第 n 个"字符到"第 m 个"字符之间的部分,包含第一个、但不包含最后一个。这种行为有违直觉,不过如果把索引想象成指向字符之间的位置,像图 8.1 那样,也许会好理解些。

Figure 8.1: Slice indices.

图 8.1:切片索引(原书插图未收录)

If you omit the first index (before the colon), the slice starts at the beginning of the string. If you omit the second index, the slice goes to the end of the string:

如果省略第一个索引(冒号之前),切片就从字符串开头算起。如果省略第二个索引,切片就一直取到字符串末尾:
fruit0070

If the first index is greater than or equal to the second the result is an empty string, represented by two quotation marks:

如果第一个索引大于或等于第二个,结果就是一个空字符串,用一对引号表示:
fruit0071

An empty string contains no characters and has length 0, but other than that, it is the same as any other string.

空字符串不含任何字符,长度为 0,除此之外它和其他任何字符串没什么两样。

Exercise 3

习题 3

Given that fruit is a string, what does fruit007 mean?

已知 fruit 是一个字符串,那么 fruit007 是什么意思?

8.5 Strings are immutable 8.5 字符串不可变

It is tempting to use the b0 operator on the left side of an assignment, with the intention of changing a character in a string. For example:

你可能会想在赋值语句的左边使用 b0 运算符,打算修改字符串里的某个字符。例如:
fruit0078

The “object” in this case is the string and the “item” is the character you tried to assign. For now, an object is the same thing as a value, but we will refine that definition later. An item is one of the values in a sequence.

这里的"对象"指的就是那个字符串,而"元素"是你试图赋值的那个字符。目前你大可以把对象fruit0)和"值"当成一回事,不过后面我们会把这个定义细化。元素b000)是序列中的某个值。

The reason for the error is that strings are immutable, which means you can’t change an existing string. The best you can do is create a new string that is a variation on the original:

出错的原因是字符串是不可变fruit0081)的,也就是说你没法改动一个已有的字符串。你顶多只能新建一个字符串,作为原字符串的某种变体:
fruit0082

This example concatenates a new first letter onto a slice of fruit008. It has no effect on the original string.

这个例子把一个新首字母拼到了 fruit008 的一个切片上。这对原字符串没有任何影响。

8.6 Searching 8.6 搜索

What does the following function do?

下面这个函数做了什么?
fruit0085

In a sense, b000 is the opposite of the b0 operator. Instead of taking an index and extracting the corresponding character, it takes a character and finds the index where that character appears. If the character is not found, the function returns b0.

从某种意义上说,b000 是 b0 运算符的反操作。它不是接收一个索引、取出对应的字符,而是接收一个字符、找出该字符出现的索引。如果没找到这个字符,函数就返回 b0。

This is the first example we have seen of a fruit0 statement inside a loop. If fruit0093, the function breaks out of the loop and returns immediately.

这是我们在循环内部见到 fruit0 语句的第一个例子。只要 fruit0095,函数就跳出循环、立即返回。

If the character doesn’t appear in the string, the program exits the loop normally and returns b0.

如果这个字符在字符串里没出现,程序就正常退出循环,返回 b0。

This pattern of computation—traversing a sequence and returning when we find what we are looking for—is called a search.

这种计算模式——遍历一个序列,找到想要的东西就返回——叫做搜索fruit0)。

Exercise 4

习题 4

Modify b000 so that it has a third parameter, the index in b000 where it should start looking.

修改 b000,让它带第三个实参,即开始在 b000 里查找的位置索引。

8.7 Looping and counting 8.7 循环与计数

The following program counts the number of times the letter b appears in a string:

下面的程序统计字母 b 在一个字符串中出现的次数:
fruit0105

This program demonstrates another pattern of computation called a counter. The variable fruit is initialized to 0 and then incremented each time an b is found. When the loop exits, fruit contains the result—the total number of b’s.

这个程序展示了另一种叫做计数器fruit01)的计算模式。变量 fruit 初始化为 0,每发现一个 b 就加 1。循环结束时,fruit 里就是结果——b 的总数。

Exercise 5

习题 5

Encapsulate this code in a function named fruit, and generalize it so that it accepts the string and the letter as arguments.

把这段代码封装进一个名为 fruit 的函数,并把它一般化,让字符串和字母都作为实参传入。

Exercise 6

习题 6

Rewrite this function so that instead of traversing the string, it uses the three-parameter version of b000 from the previous section.

重写这个函数,让它不再遍历字符串,而是使用上一节那个带三个参数的 b000 版本。

8.8 String methods 8.8 字符串方法

A method is similar to a function—it takes arguments and returns a value—but the syntax is different. For example, the method fruit takes a string and returns a new string with all uppercase letters:

方法fruit0)和函数相似——它接收实参、返回一个值——但语法不同。例如,fruit 方法接收一个字符串,返回一个把所有字母都变成大写的新字符串:

Instead of the function syntax fruit0122, it uses the method syntax fruit0123.

它用的不是函数语法 fruit0124,而是方法语法 fruit0125。
fruit0126

This form of dot notation specifies the name of the method, fruit, and the name of the string to apply the method to, b000. The empty parentheses indicate that this method takes no argument.

这种点记法标明了方法的名字 fruit,以及要对其调用方法的字符串的名字 b000。那对空括号表示这个方法不接收实参。

A method call is called an invocation; in this case, we would say that we are invoking fruit on the b000.

方法调用叫做方法调用fruit0133);在这里我们会说,我们在 b000 上调用(invoking)fruit

As it turns out, there is a string method named b000 that is remarkably similar to the function we wrote:

事实上,确实有一个名字叫 b000 的字符串方法,和我们刚才写的函数惊人地相似:
fruit0138

In this example, we invoke b000 on b000 and pass the letter we are looking for as a parameter.

在这个例子中,我们在 b000 上调用 b000,把要找的字母作为实参传进去。

Actually, the b000 method is more general than our function; it can find substrings, not just characters:

其实,b000 方法比我们的函数更通用;它不仅能找单个字符,还能找子串:
fruit0145

It can take as a second argument the index where it should start:

它可以把起始索引作为第二个实参:
fruit0146

And as a third argument the index where it should stop:

还可以把结束索引作为第三个实参:
fruit0147

This search fails because b does not appear in the index range from b to b (not including b).

这次搜索失败了,因为 b 并不出现在从 bb(不含 b)的索引范围内。

Exercise 7

习题 7

There is a string method called fruit that is similar to the function in the previous exercise. Read the documentation of this method and write an invocation that counts the number of bs in fruit015.

有一个叫做 fruit 的字符串方法,和上一道习题里的函数类似。阅读这个方法的文档,写一个方法调用,统计 fruit016 里 b 的个数。

Exercise 8

习题 8

Read the documentation of the string methods at fruit0162. You might want to experiment with some of them to make sure you understand how they work. fruit and fruit01 are particularly useful.

阅读字符串方法的文档 fruit0165。你也许想亲自动手试其中几个,确认自己理解它们的工作原理。fruitfruit01 尤其有用。

The documentation uses a syntax that might be confusing. For example, in fruit0168, the brackets indicate optional arguments. So b00 is required, but fruit is optional, and if you include fruit, then b00 is optional.

文档里用到的语法可能让人困惑。例如,在 fruit0173 中,方括号表示可选实参fruit0174)。所以 b00 是必需的,而 fruit 可选,如果你写了 fruit,那么 b00 也是可选的。

8.9 The b0 operator 8.9 b0 运算符

The word b0 is a boolean operator that takes two strings and returns b000 if the first appears as a substring in the second:

b0 是一个布尔运算符,接收两个字符串,如果第一个作为子串出现在第二个里面,就返回 b000:
fruit0185

For example, the following function prints all the letters from fruit that also appear in fruit:

例如,下面这个函数会打印出 fruit 中同时也出现在 fruit 里的所有字母:
fruit0190

With well-chosen variable names, Python sometimes reads like English. You could read this loop, “for (each) letter in (the first) word, if (the) letter (appears) in (the second) word, print (the) letter.”

只要变量名取得好,Python 读起来有时就像英语。你可以把这段循环读成:"for(每一个)letter in(第一个)word,if(这个)letter(出现)in(第二个)word,print(这个)letter。"

Here’s what you get if you compare apples and oranges:

如果你拿苹果和橘子比较,会看到这样的结果:
fruit0191

8.10 String comparison 8.10 字符串比较

The relational operators work on strings. To see if two strings are equal:

关系运算符对字符串同样适用。要判断两个字符串是否相等:
fruit0192

Other relational operations are useful for putting words in alphabetical order:

其他关系运算符则可用于把单词按字母顺序排好:
fruit0193

Python does not handle uppercase and lowercase letters the same way that people do. All the uppercase letters come before all the lowercase letters, so:

Python 处理大小写字母的方式和人不一样。所有大写字母都排在所有小写字母之前,所以:
fruit0194

A common way to address this problem is to convert strings to a standard format, such as all lowercase, before performing the comparison. Keep that in mind in case you have to defend yourself against a man armed with a Pineapple.

解决这个问题的常见办法,是先在进行比较之前,把字符串统一转成某种标准格式,比如全部小写。记住这一点,说不定哪天你需要对抗一个手持菠萝的男人时用得上。

8.11 Debugging 8.11 调试

When you use indices to traverse the values in a sequence, it is tricky to get the beginning and end of the traversal right. Here is a function that is supposed to compare two words and return b000 if one of the words is the reverse of the other, but it contains two errors:

当你用索引去遍历序列里的值时,要把遍历的起点和终点搞对并不容易。下面这个函数本应比较两个单词,如果其中一个是另一个的反转就返回 b000,但它里面藏着两个错误:
fruit0197

The first b0 statement checks whether the words are the same length. If not, we can return fruit immediately and then, for the rest of the function, we can assume that the words are the same length. This is an example of the guardian pattern in Section 6.8.

第一个 b0 语句检查两个单词长度是否相同。如果不同,我们可以立即返回 fruit,之后在函数其余部分就可以假定两个单词长度相同。这是 6.8 节里"守门人模式"的一个例子。

b and b are indices: b traverses fruit forward while b traverses fruit backward. If we find two letters that don’t match, we can return fruit immediately. If we get through the whole loop and all the letters match, we return b000.

bb 都是索引:b 正向遍历 fruit,而 b 反向遍历 fruit。如果找到两个不匹配的字母,就立即返回 fruit。如果整个循环走完、所有字母都匹配,就返回 b000。

If we test this function with the words “pots” and “stop”, we expect the return value b000, but we get an IndexError:

如果我们用 "pots" 和 "stop" 这两个词来测试这个函数,期望的返回值是 b000,但得到的却是一个 IndexError:
fruit0220

For debugging this kind of error, my first move is to print the values of the indices immediately before the line where the error appears.

要调试这类错误,我的第一个动作就是在出错那行之前,立刻把索引的值打印出来。
fruit0221

Now when I run the program again, I get more information:

现在我再运行一次,就得到了更多信息:
fruit0222

The first time through the loop, the value of b is 4, which is out of range for the string fruit0. The index of the last character is 3, so the initial value for b should be fruit0226.

第一次进入循环时,b 的值是 4,而对于字符串 fruit0 来说这已经越界了。最后一个字符的索引是 3,所以 b 的初始值应该是 fruit0230。

If I fix that error and run the program again, I get:

我把这个错误修好后再运行一次,得到:
fruit0231

This time we get the right answer, but it looks like the loop only ran three times, which is suspicious. To get a better idea of what is happening, it is useful to draw a state diagram. During the first iteration, the frame for fruit0232 is shows in Figure 8.2.

这次答案是对的,但循环似乎只跑了三次,这有点可疑。要更清楚地了解发生了什么,画一张状态图很有帮助。在第一次迭代时,fruit0233 的栈帧如图 8.2 所示。

Figure 8.2: State diagram.

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

I took a little license by arranging the variables in the frame and adding dotted lines to show that the values of b and b indicate characters in fruit and fruit.

我稍微"自由发挥"了一下,把栈帧里的变量摆了摆位置,并加了些虚线,用来表明 bb 的值分别指向 fruitfruit 里的字符。

Exercise 9

习题 9

Starting with this diagram, execute the program on paper, changing the values of b and b during each iteration. Find and fix the second error in this function.

从这张图出发,在纸上手动执行这个程序,在每次迭代中改变 bb 的值。找出并修正这个函数里的第二个错误。

8.12 Glossary 8.12 术语表

object:
Something a variable can refer to. For now, you can use “object” and “value” interchangeably.
sequence:
An ordered set; that is, a set of values where each value is identified by an integer index.
item:
One of the values in a sequence.
index:
An integer value used to select an item in a sequence, such as a character in a string.
slice:
A part of a string specified by a range of indices.
empty string:
A string with no characters and length 0, represented by two quotation marks.
immutable:
The property of a sequence whose items cannot be assigned.
traverse:
To iterate through the items in a sequence, performing a similar operation on each.
search:
A pattern of traversal that stops when it finds what it is looking for.
counter:
A variable used to count something, usually initialized to zero and then incremented.
method:
A function that is associated with an object and called using dot notation.
invocation:
A statement that calls a method.
object 对象:
变量可以指向的东西。目前你可以把"对象"和"值"当作同义词使用。
sequence 序列:
一个有序集合;也就是说,一组值,其中每个值都由一个整数索引来标识。
item 元素:
序列中的某一个值。
index 索引:
用来从序列中选出某个值(比如字符串里的某个字符)的整数值。
slice 切片:
由一段索引范围所指定的字符串的一部分。
empty string 空字符串:
不含任何字符、长度为 0 的字符串,用一对引号表示。
immutable 不可变:
序列的一种属性,指其元素不可被赋值。
traverse 遍历:
依次经过序列中的各个值,对每个值执行类似的操作。
search 搜索:
一种遍历模式,找到要找的东西时就停下来。
counter 计数器:
用来计数的变量,通常初始化为 0,然后不断递增。
method 方法:
与某个对象相关联、用点记法调用的函数。
invocation 方法调用:
调用一个方法的语句。

8.13 Exercises 8.13 习题

Exercise 10

习题 10

A string slice can take a third index that specifies the “step size;” that is, the number of spaces between successive characters. A step size of 2 means every other character; 3 means every third, etc.

字符串切片可以带第三个索引,用来指定"步长",也就是相邻字符之间跳过的间隔数。步长为 2 表示隔一个取一个字符;步长为 3 表示每隔两个取一个,依此类推。
fruit0246

A step size of -1 goes through the word backwards, so the slice fruit0 generates a reversed string.

步长为 -1 会倒着走过单词,所以切片 fruit0 会生成一个反转后的字符串。

Use this idiom to write a one-line version of fruit0249 from Exercise 6.

利用这个惯用法,写出第 6 题里 fruit0250 的单行版本。

Exercise 11

习题 11

The following functions are all intended to check whether a string contains any lowercase letters, but at least some of them are wrong. For each function, describe what the function actually does (assuming that the parameter is a string).

下面这些函数都本意是要检查一个字符串里是否含有小写字母,但至少其中一些是错的。对每个函数,描述它实际做了什么(假设参数是字符串)。
fruit0251

Exercise 12

习题 12

ROT13 is a weak form of encryption that involves “rotating” each letter in a word by 13 places. To rotate a letter means to shift it through the alphabet, wrapping around to the beginning if necessary, so ’A’ shifted by 3 is ’D’ and ’Z’ shifted by 1 is ’A’.

ROT13 是一种很弱的加密方式,方法是把单词里每个字母"旋转"13 个位置。旋转一个字母,就是让它在字母表里平移,必要时绕回开头,例如 ’A’ 平移 3 位变成 ’D’,而 ’Z’ 平移 1 位变成 ’A’。

Write a function called fruit0252 that takes a string and an integer as parameters, and that returns a new string that contains the letters from the original string “rotated” by the given amount.

写一个名为 fruit0253 的函数,接收一个字符串和一个整数作为形参,返回一个新的字符串,其中的字母按给定数量从原字符串"旋转"而来。

For example, “cheer” rotated by 7 is “jolly” and “melon” rotated by -10 is “cubed”.

例如,"cheer" 旋转 7 位变成 "jolly",而 "melon" 旋转 -10 位变成 "cubed"。

You might want to use the built-in functions b00, which converts a character to a numeric code, and b00, which converts numeric codes to characters.

你可能会用到内置函数 b00(把字符转换成数字编码)和 b00(把数字编码转换回字符)。

Potentially offensive jokes on the Internet are sometimes encoded in ROT13. If you are not easily offended, find and decode some of them. Solution: fruit0258.

互联网上那些可能冒犯人的玩笑,有时会先用 ROT13 编码。如果你不太容易被冒犯,不妨去找几个来解码试试。答案:fruit0259。