7. PY4E - Python 为所有人¶
切换导航
第 1 章:简介 第 2 章:变量 第 3 章:条件语句 第 4 章:函数 第 5 章:迭代 第 6 章:字符串 第 7 章:文件 第 8 章:列表 第 9 章:字典 第 10 章:元组 第 11 章:正则表达式 第 12 章:网络程序 第 13 章:Python 与 Web 服务 第 14 章:Python 对象 第 15 章:Python 与数据库 第 16 章:数据可视化
7.1. 字符串¶
7.1.1. 字符串是一个序列¶
字符串是字符的 序列 。你可以使用下标运算符逐个访问字符:
>>> fruit = 'banana'
>>> letter = fruit[1]
第二个语句从变量 fruit 中提取索引位置 1 处的字符,并将其赋值给变量 letter 。
方括号中的表达式称为 索引 。索引指示您想获取序列中的哪个字符(因此得名)。
但您得到的可能并非预期结果:
>>> print(letter)
a
对于大多数人来说,"banana" 的首字母是 "b",而不是 "a"。但在 Python 中,索引是从字符串开头计算的偏移量,且首字母的偏移量为零。
>>> letter = fruit[0]
>>> print(letter)
b
因此,"b" 是 "banana" 的第 0 个字母("zero-th"),"a" 是第 1 个字母("one-th"),而 "n" 是第 2 个("two-th")字母。
字符串索引
您可以使用任何表达式(包括变量和运算符)作为索引,但索引的值必须是整数。否则您将得到:
>>> letter = fruit[1.5]
TypeError: string indices must be integers
7.1.2. 使用 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] 返回倒数第二个字符,依此类推。
7.1.3. 使用循环遍历字符串¶
许多计算都涉及逐个字符处理字符串。它们通常从开头开始,依次选择每个字符,对其进行某种操作,然后继续直到结尾。这种处理模式称为 遍历 。编写遍历的一种方法是使用 while 循环:
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index = index + 1
该循环遍历字符串,并在每一行单独显示每个字母。循环条件为 index < len(fruit) ,因此当 index 等于字符串的长度时,条件为假,循环体不会被执行。最后访问的字符是索引为 len(fruit)-1 的字符,即字符串中的最后一个字符。
练习 1:编写一个 ``while`` 循环,从字符串的最后一个字符开始,一直工作到字符串的第一个字符,将每个字母打印在单独的一行上,除了 backwards。
另一种遍历写法是使用一个 for 循环:
for char in fruit:
print(char)
每次循环时,字符串中的下一个字符被赋值给变量 char 。循环持续进行,直到没有剩余字符为止。
7.1.4. 字符串切片¶
字符串的片段称为一个 切片 。选择切片类似于选择字符:
>>> 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'
如果第一个索引大于或等于第二个索引,则结果为 空字符串 ,由两个引号表示:
>>> fruit = 'banana'
>>> fruit[3:3]
''
空字符串不包含任何字符且长度为 0,但除此之外,它与其他任何字符串相同。
练习 2:已知 ``fruit`` 是一个字符串,那么 ``fruit[:]`` 是什么意思?
7.1.5. 字符串是不可变的¶
使用赋值运算符左侧的运算符来改变字符串中的字符是诱人的。例如:
>>> greeting = 'Hello, world!'
>>> greeting[0] = 'J'
TypeError: 'str' object does not support item assignment
本例中的"object"是字符串,而"item"是你试图赋值的字符。目前, object 与值(value)是同一概念,但我们将稍后细化该定义。 item 是序列中的一个值。
出现错误的原因是字符串是 immutable (不可变)的,这意味着你不能修改现有的字符串。你能做的最好的事情是创建一个基于原始字符串的变体新字符串:
>>> greeting = 'Hello, world!'
>>> new_greeting = 'J' + greeting[1:]
>>> print(new_greeting)
Jello, world!
本示例将一个新首字母拼接至 greeting 切片,对原始字符串无影响。
7.1.6. 循环与计数¶
下面的程序统计字符串中字母"a"出现的次数:
word = 'banana'
count = 0
for letter in word:
if letter == 'a':
count = count + 1
print(count)
本程序演示了另一种称为 计数器 的计算模式。变量 count 被初始化为 0,然后每当发现一个"a"时便递增。当循环退出时, count 包含结果:a 的总数。
练习 3:将此代码封装到名为 ``count`` 的函数中,并将其泛化,使其接受字符串和字母作为参数。
7.1.7. in 运算符¶
单词 in 是一个布尔运算符,它接收两个字符串,如果第一个字符串作为子串出现在第二个字符串中,则返回 True :
>>> 'a' in 'banana'
True
>>> 'seed' in 'banana'
False
7.1.8. 字符串比较¶
比较运算符作用于字符串。要查看两个字符串是否相等:
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.
解决此问题的一个常用方法是在进行比较之前将字符串转换为标准格式,例如全部转换为小写。请记住这一点,以防你需要防御一个手持菠萝的武装人员。
7.1.9. String 方法¶
字符串是 Python 对象 的一个示例。对象既包含数据(实际的字符串本身),也包含 方法 ,这些方法实际上是内置于对象中的函数,并且可供对象的任何 实例 使用。
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(...)
S.capitalize() -> str
Return a capitalized version of S, i.e. make the first character
have upper case and the rest lower case.
>>>
虽然 dir 函数列出了方法,而你可以使用 help 获取某个方法的简单文档,但字符串方法更好的文档来源是 https://docs.python.org/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/library/stdtypes.html#string-methods
编写一个调用,统计字母 a 在"banana"中出现的次数。
7.1.10. 解析字符串¶
通常,我们想要检查一个字符串并查找子串。例如,如果我们被提供如下格式的系列行:
From stephen.marquard@ `` uct.ac.za`` `` Sat Jan 5 09:14:16 2008``
而且如果我们只想从每一行中提取地址的后半部分(即, uct.ac.za ),我们可以通过使用 find 方法和字符串切片来实现。
首先,我们将查找字符串中 at 符号的位置。然后,我们将查找 at 符号之后的第一个空格的位置。最后,我们将使用字符串切片来提取我们要查找的字符串部分。
>>> data = 'From stephen.marquard@uct.ac.za 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 从此处开始查找。当我们切片时,我们提取“从 at 符号之后第一个字符开始,直到空格字符(但不包括该空格字符)”之间的字符。
find 方法的文档可在
https://docs.python.org/library/stdtypes.html#string-methods.
7.1.11. 格式化运算符¶
格式化运算符 % 允许我们构建字符串,将字符串中的部分替换为变量中存储的数据。当应用于整数时, % 是取模运算符。但当第一个操作数是字符串时, % 则是格式化运算符。
第一个操作数是 格式字符串 ,其中包含一个或多个 格式序列 ,用于指定第二个操作数的格式化方式。结果是字符串。
例如,格式序列 %d 意味着第二个操作数应格式化为整数(“d”代表“十进制”):
>>> camels = 42
>>> '%d' % camels
'42'
结果是字符串 '42',不要将其与整数值 42 混淆。
格式序列可以出现在字符串的任何位置,因此您可以在句子中嵌入一个值:
>>> camels = 42
>>> 'I have spotted %d camels.' % camels
'I have spotted 42 camels.'
如果字符串中存在多个格式序列,则第二个参数必须是一个元组:sup:`1 <https://www.py4e.com/html3/06-strings#fn1>`__`。每个格式序列按顺序与元组中的一个元素进行匹配。
之前的示例使用 %d 格式化整数,使用 %g 格式化浮点数(别问为什么),使用 %s 格式化字符串:
>>> 'In %d years I have spotted %g %s.' % (3, 0.1, 'camels')
'In 3 years I have spotted 0.1 camels.'
元组中的元素数量必须与字符串中的格式序列数量相匹配。元素的类型也必须与格式序列相匹配:
>>> '%d %d %d' % (1, 2)
TypeError: not enough arguments for format string
>>> '%d' % 'dollars'
TypeError: %d format: a number is required, not str
在第一个示例中,元素数量不足;在第二个示例中,元素的类型不正确。
格式运算符功能强大,但使用起来可能比较困难。您可以在以下地址阅读更多相关内容:
https://docs.python.org/library/stdtypes.html#printf-style-string-formatting.
7.1.12. 调试¶
你在编程时应培养的一种技能是始终自问:“这里可能出什么问题?”或者换一种说法:“我们的用户可能会做什么疯狂的事情来使我们的(看似)完美程序崩溃?”
例如,查看我们在迭代章节中用于演示 while 循环的程序:
while True:
line = input('> ')
if line[0] == '#':
continue
if line == 'done':
break
print(line)
print('Done!')
# Code: http://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('#'):
另一种方法是使用 guardian 模式安全地编写 if 语句,并确保第二个逻辑表达式仅在字符串中至少有一个字符时被评估:
if len(line) > 0 and line[0] == '#':
7.1.13. 术语表¶
counter 用于计数某个事物的变量,通常初始化为零,然后递增。
empty string 一个没有任何字符且长度为 0 的字符串,用两个引号表示。
format operator 一个运算符 % ,它接受一个格式字符串和一个元组,生成一个包含按格式字符串指定的元组元素格式化后的字符串。
format sequence 格式字符串中的一串字符,例如 %d ,用于指定值应如何格式化。
format string 与格式运算符一起使用的字符串,其中包含格式序列。
flag 一个布尔变量,用于指示某个条件是真还是假。
invocation 调用方法的语句。
immutable 序列的属性,其项目不能被赋值。
index 用于在序列中选择项目的整数值,例如字符串中的一个字符。
item 序列中的一个值。
method 与对象关联并使用点符号调用的函数。
object 变量可以引用的事物。目前,你可以将“对象”和“值”互换使用。
search 一种遍历模式,在找到所寻找的内容时停止。
sequence 有序集合;即一组值,其中每个值都由一个整数索引标识。
slice 由索引范围指定的字符串的一部分。
traverse 遍历序列中的项目并对每个项目执行类似操作的迭代过程。
7.1.14. 练习¶
练习 5:将以下存储字符串的 Python 代码:
str = 'X-DSPAM-Confidence: ``0.8475`` '
使用 ``find`` 和字符串切片提取冒号字符后的字符串部分,然后使用 ``float`` 函数将提取的字符串转换为浮点数。
练习 6:阅读字符串方法的文档 https://docs.python.org/library/stdtypes.html#string-methods 您可能希望尝试其中一些方法,以确保您理解它们的工作原理。 ``strip`` 和 ``replace`` 特别有用。
之前的文档使用了可能令人困惑的语法。例如,在 find(sub[, start[, end]]) 中,方括号表示可选参数。因此 sub 是必需的,但 start 是可选的,如果你包含 start ,那么 end 就是可选的。
元组是括号内逗号分隔值序列。我们将在第 10 章↩︎ 中介绍元组。
如果您在本书中发现错误,欢迎使用 Github 发送修正。
