Skip to main content

06-strings

好的,这是“字符串”部分的简体中文重写,保持了原有的语义和 Markdown 格式:

字符串

字符串是一个序列

字符串是字符的序列 (sequence)。你可以使用方括号运算符一次访问一个字符:

>>> fruit = 'banana'
>>> letter = fruit[1]

第二个语句从 fruit 变量中提取索引位置为 1 的字符,并将其赋给 letter 变量。

方括号中的表达式称为索引 (index)。索引指示你想要序列中的哪个字符(因此得名)。

但你可能得不到你期望的结果:

>>> print(letter)
a

对大多数人来说,“banana”的第一个字母是“b”,而不是“a”。但在 Python 中,索引是从字符串开头的偏移量,第一个字母的偏移量是零。

>>> letter = fruit[0]
>>> print(letter)
b

所以“b”是“banana”的第 0 个字母(“零号”),“a”是第 1 个字母(“一号”),“n”是第 2 个字母(“二号”)。

字符串索引 字符串索引

你可以使用任何表达式,包括变量和运算符,作为索引,但索引的值必须是整数。否则你会得到:

>>> letter = fruit[1.5]
TypeError: string indices must be integers

使用 len 获取字符串长度

len 是一个内置函数,返回字符串中的字符数:

>>> fruit = 'banana'
>>> len(fruit)
6

要获取字符串的最后一个字母,你可能会想尝试这样做:

>>> length = len(fruit)
>>> last = fruit[length]
IndexError: string index out of range

出现 IndexError 的原因是“banana”中没有索引为 6 的字母。由于我们从零开始计数,六个字母的编号是 0 到 5。要获取最后一个字符,你必须从 length 中减去 1:

>>> last = fruit[length-1]
>>> print(last)
a

或者,你可以使用负数索引,它从字符串末尾向前计数。表达式 fruit[-1] 得到最后一个字母,fruit[-2] 得到倒数第二个字母,依此类推。

使用循环遍历字符串

许多计算涉及一次处理字符串中的一个字符。通常它们从开头开始,依次选择每个字符,对其进行某些操作,然后继续直到结束。这种处理模式称为遍历 (traversal)。编写遍历的一种方法是使用 while 循环:

index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index = index + 1

这个循环遍历字符串并在单独的行上显示每个字母。循环条件是 index < len(fruit),所以当 index 等于字符串的长度时,条件为假,循环体不执行。访问的最后一个字符是索引为 len(fruit)-1 的字符,也就是字符串中的最后一个字符。

练习 1: 编写一个 while 循环,从字符串的最后一个字符开始,反向工作到字符串的第一个字符,将每个字母打印在单独的行上,但是是反向的。

另一种编写遍历的方法是使用 for 循环:

for char in fruit:
print(char)

每次循环,字符串中的下一个字符都会被赋给变量 char。循环一直持续到没有字符剩下。

字符串切片

字符串的一部分称为切片 (slice)。选择切片类似于选择字符:

>>> s = 'Monty Python'
>>> print(s[0:5])
Monty
>>> print(s[6:12])
Python

运算符 [n:m] 返回字符串中从第“n”个字符到第“m”个字符的部分,包括第一个但不包括最后一个。

如果你省略第一个索引(冒号之前),切片从字符串的开头开始。如果你省略第二个索引,切片一直到字符串的末尾:

>>> fruit = 'banana'
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'

如果第一个索引大于或等于第二个索引,结果是一个空字符串 (empty string),用两个引号表示:

>>> fruit = 'banana'
>>> fruit[3:3]
''

空字符串不包含任何字符,长度为 0,但除此之外,它与任何其他字符串相同。

练习 2: 假设 fruit 是一个字符串,fruit[:] 是什么意思?

字符串是不可变的

很诱人地想在赋值语句的左侧使用 [] 运算符,意图改变字符串中的一个字符。例如:

>>> greeting = 'Hello, world!'
>>> greeting[0] = 'J'
TypeError: 'str' object does not support item assignment

这种情况下的“对象”是字符串,“项目”是你试图赋值的字符。目前,对象 (object) 与值的含义相同,但我们稍后会完善这个定义。项目 (item) 是序列中的值之一。

出现错误的原因是字符串是不可变的 (immutable),这意味着你不能更改现有的字符串。你能做的最好的事情是创建一个新字符串,它是原始字符串的变体:

>>> greeting = 'Hello, world!'
>>> new_greeting = 'J' + greeting[1:]
>>> print(new_greeting)
Jello, world!

这个例子将一个新的首字母连接到 greeting 的一个切片上。它对原始字符串没有影响。

循环和计数

以下程序计算字母“a”在字符串中出现的次数:

word = 'banana'
count = 0
for letter in word:
if letter == 'a':
count = count + 1
print(count)

这个程序演示了另一种称为计数器 (counter) 的计算模式。变量 count 初始化为 0,然后在每次找到“a”时递增。当循环退出时,count 包含结果:a 的总数。

练习 3: 将此代码封装在一个名为 count 的函数中,并对其进行通用化,使其接受字符串和字母作为参数。

in 运算符

单词 in 是一个布尔运算符,它接受两个字符串,如果第一个字符串作为子字符串出现在第二个字符串中,则返回 True

>>> 'a' in 'banana'
True
>>> 'seed' in 'banana'
False

字符串比较

比较运算符适用于字符串。要查看两个字符串是否相等:

if word == 'banana':
print('All right, bananas.')

其他比较操作对于按字母顺序排列单词很有用:

if word < 'banana':
print('Your word,' + word + ', comes before banana.')
elif word > 'banana':
print('Your word,' + word + ', comes after banana.')
else:
print('All right, bananas.')

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

Your word, Pineapple, comes before banana.

解决此问题的一种常用方法是在执行比较之前将字符串转换为标准格式,例如全部小写。如果你需要防御一个拿着菠萝(Pineapple)的人,请记住这一点。

字符串方法

字符串是 Python 对象 (objects) 的一个例子。一个对象既包含数据(实际的字符串本身),也包含方法 (methods),这些方法实际上是内置于对象中的函数,可供该对象的任何实例 (instance) 使用。

Python 有一个名为 dir 的函数,它列出了对象可用的方法。type 函数显示对象的类型,dir 函数显示可用的方法。

>>> stuff = 'Hello world'
>>> type(stuff)
<class 'str'>
>>> dir(stuff)
[... 'capitalize', 'casefold', 'center', 'count', 'encode',\
'endswith', 'expandtabs', 'find', 'format', 'format_map',\
'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit',\
'isidentifier', 'islower', 'isnumeric', 'isprintable',\
'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower',\
'lstrip', 'maketrans', 'partition', 'replace', 'rfind',\
'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip',\
'split', 'splitlines', 'startswith', 'strip', 'swapcase',\
'title', 'translate', 'upper', 'zfill']
>>> help(str.capitalize)
Help on method_descriptor:

capitalize(self, /)
Return a capitalized version of the string.

More specifically, make the first character have upper
case and the rest lower case.
>>>

虽然 dir 函数列出了方法,你可以使用 help 获取方法的简单文档,但更好的字符串方法文档来源是:

https://docs.python.org/zh-cn/3/library/stdtypes.html#string-methods

调用方法类似于调用函数(它接受参数并返回值),但语法不同。我们通过将方法名附加到变量名后面,并使用句点作为分隔符来调用方法。

例如,方法 upper 接受一个字符串并返回一个所有字母都大写的新字符串:

它不使用函数语法 upper(word),而是使用方法语法 word.upper()

>>> word = 'banana'
>>> new_word = word.upper()
>>> print(new_word)
BANANA

这种点表示法形式指定了方法名 upper 和要应用该方法的字符串名 word。空括号表示此方法不接受任何参数。

方法调用称为调用 (invocation);在这种情况下,我们会说我们在 word 上调用了 upper

例如,有一个名为 find 的字符串方法,它搜索一个字符串在另一个字符串中的位置:

>>> word = 'banana'
>>> index = word.find('a')
>>> print(index)
1

在这个例子中,我们在 word 上调用 find,并将要查找的字母作为参数传递。

find 方法可以查找子字符串以及字符:

>>> word.find('na')
2

它可以接受第二个参数,即它应该开始搜索的索引:

>>> word.find('na', 3)
4

一个常见的任务是使用 strip 方法从字符串的开头和结尾移除空白(空格、制表符或换行符):

>>> line = '  Here we go  '
>>> line.strip()
'Here we go'

一些方法,如 startswith,返回布尔值。

>>> line = 'Have a nice day'
>>> line.startswith('Have')
True
>>> line.startswith('h')
False

你会注意到 startswith 要求大小写匹配,所以有时我们会在进行任何检查之前,使用 lower 方法将一行全部转换为小写。

>>> line = 'Have a nice day'
>>> line.startswith('h')
False
>>> line.lower()
'have a nice day'
>>> line.lower().startswith('h')
True

在最后一个例子中,调用了 lower 方法,然后我们使用 startswith 来查看结果的小写字符串是否以字母“h”开头。只要我们小心顺序,我们可以在单个表达式中进行多次方法调用。

练习 4: 有一个名为 count 的字符串方法,它与上一个练习中的函数类似。请阅读此方法的文档:

https://docs.python.org/zh-cn/3/library/stdtypes.html#string-methods

编写一个调用,计算字母 a 在 “banana” 中出现的次数。

解析字符串

通常,我们想查看一个字符串并找到一个子字符串。例如,如果我们遇到一系列格式如下的行:

From stephen.marquard@uct.ac.za Sat Jan 5 09:14:16 2008

并且我们只想从每行中提取地址的后半部分(即 uct.ac.za),我们可以通过使用 find 方法和字符串切片来做到这一点。

首先,我们将找到字符串中 @ 符号的位置。然后我们将找到 @ 符号之后第一个空格的位置。然后我们将使用字符串切片来提取我们正在寻找的字符串部分。

>>> data = 'From [email protected] Sat Jan  5 09:14:16 2008'
>>> atpos = data.find('@')
>>> print(atpos)
21
>>> sppos = data.find(' ',atpos)
>>> print(sppos)
31
>>> host = data[atpos+1:sppos]
>>> print(host)
uct.ac.za
>>>

我们使用了 find 方法的一个版本,它允许我们指定字符串中我们希望 find 开始查找的位置。当我们进行切片时,我们提取从“@符号之后一位到空格字符之前(但不包括空格字符)”的字符。

find 方法的文档可在以下位置找到:

https://docs.python.org/zh-cn/3/library/stdtypes.html#string-methods

格式化字符串字面量

格式化字符串字面量(通常简称为 f-string)允许在字符串字面量中使用 Python 表达式。这是通过在字符串字面量前加上 f 并将表达式放在花括号 {} 中来实现的。

例如,在 f-string 中用花括号括起变量名将导致它被其值替换:

>>> camels = 42
>>> f'{camels}'
'42'

结果是字符串 '42',不要与整数值 42 混淆。

表达式可以出现在字符串中的任何位置,因此你可以将值嵌入到句子中:

>>> camels = 42
>>> f'I have spotted {camels} camels.'
'I have spotted 42 camels.'

可以在单个字符串字面量中包含多个表达式,以创建更复杂的字符串。

>>> years = 3
>>> count = .1
>>> species = 'camels'
>>> f'In {years} years I have spotted {count} {species}.'
'In 3 years I have spotted 0.1 camels.'

格式化字符串字面量功能强大,它们能做的甚至比这里介绍的还要多。你可以在以下网址阅读更多相关内容:

https://docs.python.org/zh-cn/3/tutorial/inputoutput.html#formatted-string-literals

调试

在你编程时应该培养的一项技能是总是问自己:“这里可能出什么问题?”或者,“我们的用户可能会做出什么疯狂的事情来让 我们(看似)完美的程序崩溃?”

例如,看看我们在迭代章节中用来演示 while 循环的程序:

while True:
line = input('> ')
if line[0] == '#':
continue
if line == 'done':
break
print(line)
print('Done!')

# 代码: https://www.py4e.com/code3/copytildone2.py

看看当用户输入空行时会发生什么:

> hello there
hello there
> # don't print this
> print this!
print this!
>
Traceback (most recent call last):
File "copytildone.py", line 3, in <module>
if line[0] == '#':
IndexError: string index out of range

代码运行良好,直到遇到空行。那时没有第零个字符,所以我们得到一个回溯信息。有两种解决方案可以使第三行即使在行为空的情况下也“安全”。

一种可能性是简单地使用 startswith 方法,如果字符串为空,它会返回 False

if line.startswith('#'):

另一种方法是使用守护模式安全地编写 if 语句,并确保第二个逻辑表达式仅在字符串中至少有一个字符时才被评估:

if len(line) > 0 and line[0] == '#':

术语表

计数器 (counter) 用于计算某事物数量的变量,通常初始化为零然后递增。 空字符串 (empty string) 没有字符且长度为 0 的字符串,用两个引号表示。 标志 (flag) 用于指示条件是真还是假的布尔变量。 调用 (invocation) 调用方法的语句。 不可变的 (immutable) 序列的一种属性,其项目不能被赋值更改。 索引 (index) 用于选择序列中项目(例如字符串中的字符)的整数值。 项目 (item) 序列中的值之一。 方法 (method) 与对象关联并使用点表示法调用的函数。 对象 (object) 变量可以引用的东西。目前,你可以互换使用“对象”和“值”。 搜索 (search) 一种遍历模式,当找到它正在寻找的东西时停止。 序列 (sequence) 一个有序集合;即一组值,其中每个值由整数索引标识。 切片 (slice) 由索引范围指定的字符串的一部分。 遍历 (traverse) 迭代序列中的项目,对每个项目执行类似的操作。

练习

练习 5: 字符串切片

获取以下存储字符串的 Python 代码:

str = 'X-DSPAM-Confidence: 0.8475'

使用 find 和字符串切片提取冒号字符之后的部分字符串,然后使用 float 函数将提取的字符串转换为浮点数。

练习 6: 字符串方法

阅读字符串方法的文档:

https://docs.python.org/zh-cn/3/library/stdtypes.html#string-methods

你可能想尝试其中一些方法,以确保你理解它们的工作原理。stripreplace 特别有用。

文档使用了一种可能令人困惑的语法。例如,在 find(sub[, start[, end]]) 中,方括号表示可选参数。所以 sub 是必需的,但 start 是可选的,如果你包含 start,那么 end 也是可选的。


如果你在本书中发现错误,欢迎使用 Github 给我发送修正。