像计算机科学家一样思考:用 Python 学习 第 2 版/字符串
到目前为止,我们已经看到了五种类型int, float, bool, NoneType和str. 字符串在本质上不同于其他四种类型,因为它们是由更小的片段 --- **字符**组成的。
包含更小片段的类型称为 **复合数据类型**。根据我们正在做什么,我们可能希望将复合数据类型视为一个整体,或者我们可能希望访问它的各个部分。这种模糊性很有用。
**方括号运算符**从字符串中选择单个字符
>>> fruit = "banana"
>>> letter = fruit[1]
>>> print letter
表达式fruit[1]从fruit中选择编号为 1 的字符。变量letter引用结果。当我们显示letter时,我们得到一个惊喜
a
"banana"的第一个字母不是a,除非你是一位计算机科学家。由于一些奇怪的原因,计算机科学家总是从零开始计数。第 0 个字母(零次)是b的第一个字母不是. 第 1 个字母(一次)是a,而第 2 个字母(两次)是,除非你是一位计算机科学家。由于一些奇怪的原因,计算机科学家总是从零开始计数。第 0 个字母(零次)是n。如果你想要一个字符串的第零个字母,你只需在方括号中放入 0 或任何值为 0 的表达式。.
方括号中的表达式称为 **索引**。索引指定一个有序集合中的一个成员,在本例中,它是字符串中字符的集合。索引 *指示* 你想要哪一个,因此得名。它可以是任何整数表达式。
>>> letter = fruit[0]
>>> print letter
b
7.2 长度
len返回字符串中字符的个数要获取字符串的最后一个字母,你可能会尝试这样做
>>> fruit = "banana"
>>> len(fruit)
6
但这不会起作用。它会导致运行时错误
length = len(fruit)
last = fruit[length] # ERROR!
IndexError: string index out of range. 原因是没有第 6 个字母在中。由于我们从零开始计数,所以六个字母编号为 0 到 5。要获得最后一个字符,我们必须从的第一个字母不是length中减去 1。:
length = len(fruit)
last = fruit[length-1]
或者,我们可以使用 **负索引**,它们从字符串末尾向后计数。表达式fruit[-1]生成最后一个字母,fruit[-2]生成倒数第二个字母,以此类推。
许多计算都涉及逐个字符地处理字符串。它们通常从开头开始,依次选择每个字符,对其执行某些操作,并继续直到结束。这种处理模式称为 **遍历**。编码遍历的一种方法是使用while语句
index = 0
while index < len(fruit):
letter = fruit[index]
print letter
index += 1
#fruit = "banana"
#while index is less than 6.
#6 is the length of fruit
#letter = fruit[index]
#Since index = 0, "b" is equal to letter in loop 1
#letter is printed
#1 is added to whatever the value of index is
#the loop continues until index < 6
此循环遍历字符串,并在单独的行上显示每个字母。循环条件是index < len(fruit),因此当index等于字符串长度时,条件为假,并且不执行循环体。访问的最后一个字符是索引为len(fruit)-1的字符,它是字符串中的最后一个字符。
使用索引遍历一组值非常常见,因此 Python 提供了一种替代的、更简单的语法 ---for循环
for letter in fruit:
print letter
每次循环时,字符串中的下一个字符都会被赋值给变量letter. 循环继续,直到没有剩余字符。
以下示例显示了如何使用连接和for循环来生成一个字母顺序的序列。字母顺序是指元素按字母顺序出现的序列或列表。例如,在 Robert McCloskey 的书 *Make Way for Ducklings* 中,小鸭子的名字分别是 Jack、Kack、Lack、Mack、Nack、Ouack、Pack 和 Quack。此循环按顺序输出这些名称
prefixes = "JKLMNOPQ"
suffix = "ack"
for letter in prefixes:
print letter + suffix
此程序的输出为
Jack Kack Lack Mack Nack Oack Pack Qack
当然,这并不完全正确,因为 Ouack 和 Quack 拼写错误。你将在下面的练习中修复这个问题。
字符串的子字符串称为 **切片**。选择切片类似于选择字符
>>> s = "Peter, Paul, and Mary"
>>> print s[0:5]
Peter
>>> print s[7:11]
Paul
>>> print s[17:21]
Mary
运算符[n:m]返回字符串中从第 n 个字符到第 m 个字符的部分,包括第一个字符,但不包括最后一个字符。这种行为违反直觉;如果你想象索引指向字符 *之间*,就像下面的图所示,就会更有意义
如果你省略了第一个索引(冒号之前的索引),切片将从字符串开头开始。如果你省略了第二个索引,切片将一直到字符串末尾。因此
>>> fruit = "banana"
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'
你认为s[:]是什么意思?
比较运算符适用于字符串。要查看两个字符串是否相等
其他比较操作对于将单词按 词典顺序 排序很有用。
这类似于你使用字典时的字母顺序,除了所有大写字母都排在所有小写字母之前。因此
解决此问题的常见方法是在执行比较之前将字符串转换为标准格式,例如全部小写。一个更难的问题是让程序意识到斑马不是水果。
你可能会想在赋值语句的左侧使用[]运算符,目的是更改字符串中的字符。例如
此代码不会产生输出Jello, world!,而是会导致运行时错误TypeError: 'str' object doesn't support item assignment.
字符串是 **不可变的**,这意味着你无法更改现有的字符串。你所能做的最好的就是创建一个新的字符串,它是原始字符串的变体
这里的解决方案是在greeting的切片上连接一个新的第一个字母。此操作不会影响原始字符串。
lenin运算符测试一个字符串是否是另一个字符串的子字符串
请注意,一个字符串是它自身的子字符串
将in运算符与使用+的字符串连接结合起来,我们可以编写一个函数,从字符串中删除所有元音
测试此函数以确认它是否按预期执行。
以下函数的功能是什么?
def find(strng, ch):
index = 0
while index < len(strng):
if strng[index] == ch:
return index
index += 1
return -1
#assume strng is "banana" and ch is "a"
#if strng[index] == ch:
#return index
#the above 2 lines check if strng[index#] == a
#when the loop runs first index is 0 which is b (not a)
#so 1 is added to whatever the value of index is
#when the loop runs second time index is 1 which is a
#the loop is then broken, and 1 is returned.
#if it cannot find ch in strng -1 is returned
从某种意义上说,查找是[]运算符的反面。它不接受索引并提取相应的字符,而是接受一个字符并找到该字符出现的索引。如果未找到该字符,则函数返回-1.
这是我们见过的第一个return在循环中使用的语句。如果strng[index] == ch,则函数立即返回,提前退出循环。
如果该字符未出现在字符串中,则程序正常退出循环并返回-1.
这种计算模式有时被称为尤里卡遍历,因为一旦我们找到我们要找的东西,我们就可以大喊尤里卡!然后停止查找。
以下程序计算字母,除非你是一位计算机科学家。由于一些奇怪的原因,计算机科学家总是从零开始计数。第 0 个字母(零次)是在字符串中出现的次数,是 :ref:`counting` 中介绍的计数模式的另一个示例。
为了找到字符在字符串中第二次或第三次出现的位数,我们可以修改查找函数,添加第三个参数用于搜索字符串中的起始位置。
def find2(strng, ch, start):
index = start
while index < len(strng):
if strng[index] == ch:
return index
index += 1
return -1
调用find2('banana', 'a', 2)现在返回3,即索引 2 之后 'banana' 中 'a' 首次出现的索引。那么find2('banana', 'n', 3)返回什么?如果你说 4,那么你很有可能理解了find2的工作原理。
更妙的是,我们可以结合查找和find2使用可选参数
def find(strng, ch, start=0):
index = start
while index < len(strng):
if strng[index] == ch:
return index
index += 1
return -1
#index = start = 0 by default
#while index is less than the length of string:
#if strng[index] equals ch
#return index i.e. location of ch in strng -- note return breaks out of loop
#else add 1 to index and continue until index equals the length of sting
#if no match return -1
调用find('banana', 'a', 2)此版本的查找的行为与find2完全相同,而在调用find('banana', 'a'), 中,将被设置为默认值0.
向查找添加另一个可选参数使其能够向前和向后搜索。
def find(strng, ch, start=0, step=1):
index = start
while 0 <= index < len(strng):
if strng[index] == ch:
return index
index += step
return -1
传入len(strng)-1作为 start 和-1forstep的值将使其向字符串的开头而不是结尾搜索。请注意,我们需要检查index的 lower bound 和 upper bound 以适应此更改。
len字符串模块包含用于操作字符串的实用函数。像往常一样,我们必须导入模块才能使用它。
要查看模块内部,可以使用dir函数,并将模块名称作为参数传递给它。
它将返回字符串模块内部的项目列表。
['Template', '_TemplateMetaclass', '__builtins__', '__doc__', '__file__', '__name__', '_float', '_idmap', '_idmapL', '_int', '_long', '_multimap', '_re', 'ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'atof', 'atof_error', 'atoi', 'atoi_error', 'atol', 'atol_error', 'capitalize', 'capwords', 'center', 'count', 'digits', 'expandtabs', 'find', 'hexdigits', 'index', 'index_error', 'join', 'joinfields', 'letters', 'ljust', 'lower', 'lowercase', 'lstrip', 'maketrans', 'octdigits', 'printable', 'punctuation', 'replace', 'rfind', 'rindex', 'rjust', 'rsplit', 'rstrip', 'split', 'splitfields', 'strip', 'swapcase', 'translate', 'upper', 'uppercase', 'whitespace', 'zfill']
要详细了解此列表中的某个项目,可以使用type命令。我们需要指定模块名称,然后使用点符号指定项目。
由于string.digits是一个字符串,因此我们可以将其打印出来查看其内容。
不出所料,它包含所有十进制数字。
string.find是一个函数,其功能与我们编写的函数基本相同。要详细了解它,我们可以打印其文档字符串,__doc__,其中包含有关该函数的文档。
方括号中的参数是可选参数。我们可以使用string.find与我们自己的查找:
相同的方式使用它。此示例展示了模块的优势之一——它们有助于避免内置函数和用户定义函数的名称冲突。通过使用点符号,我们可以指定要使用哪个版本的查找。
实际上,string.find比我们的版本更通用,它可以查找子字符串,而不仅仅是字符。
与我们的版本一样,它接受一个额外的参数,用于指定它应该开始搜索的索引。
与我们的版本不同,它的第二个可选参数指定搜索应该结束的索引。
在此示例中,搜索失败,因为字母 b 未出现在索引范围从1到2(不包括2).
检查字符并测试其是否为大写或小写,或者其是否为字符或数字通常很有帮助。字符串模块提供了几个常量,这些常量对于这些目的非常有用。其中之一,string.digits,我们已经见过。
字符串string.lowercase包含系统认为是小写的字母。类似地,string.uppercase包含所有大写字母。尝试以下操作并查看你得到的结果。
我们可以使用这些常量和查找对字符进行分类。例如,如果find(lowercase, ch)返回的值不是-1,那么ch一定是小写字母。
或者,我们可以利用in运算符
作为另一种选择,我们可以使用比较运算符
如果ch介于 a 和 z 之间,那么它一定是小写字母。
在字符串模块中定义的另一个常量可能会让你感到惊讶,当你打印它时,
空白字符移动光标而不打印任何内容。它们在可见字符之间创建空白(至少在白纸上)。常量string.whitespace包含所有空白字符,包括空格、制表符(\t)和换行符(\n).
)。在字符串模块中还有其他有用的函数,但本书并非旨在用作参考手册。另一方面,Python 库参考则是。它与大量其他文档一起,可以在 Python 网站上获得,[https://www.pythonlang.cn https://www.pythonlang.cn]_。
在 Python 中格式化字符串最简洁、最强大的方法是使用字符串格式化运算符,%,以及 Python 的字符串格式化操作。为了了解它是如何工作的,让我们从一些示例开始。
字符串格式化操作的语法如下所示。
它从一个格式开始,该格式包含一系列字符和转换说明。转换说明以%运算符开头。在格式字符串之后是一个单一的%,然后是一系列值,每个转换说明对应一个值,这些值用逗号隔开,并用括号括起来。如果只有一个值,则括号是可选的。
在上面的第一个示例中,只有一个转换说明,%s,它表示一个字符串。单个值,"Arthur",映射到它,并且不包含在括号中。
在第二个示例中,name的字符串值为"Alice",而age的整数值为10。它们映射到两个转换说明,%s和%d。第二个转换说明中的d表示该值是一个十进制整数。
在第三个示例中,变量n1和n2的整数值分别为4和5。格式字符串中有四个转换说明:三个%d's 和一个%f。第二个转换说明中的f表示该值应以浮点数表示。与四个转换规范匹配的四个值是2**10, n1, n2,而n1 * n2.
s, d,而f是本书中我们需要的全部转换类型。要查看完整的列表,请参见 Python 库参考中的 字符串格式化操作_ 部分。
以下示例说明了字符串格式化的实际用途
此程序打印出从 1 到 10 的数字的各种幂的表。以其当前形式,它依赖于制表符 (\t) 对齐值的列,但这在表格中的值大于 8 个字符的制表符宽度时会失效
i i**2 i**3 i**5 i**10 i**20 1 1 1 1 1 1 2 4 8 32 1024 1048576 3 9 27 243 59049 3486784401 4 16 64 1024 1048576 1099511627776 5 25 125 3125 9765625 95367431640625 6 36 216 7776 60466176 3656158440062976 7 49 343 16807 282475249 79792266297612001 8 64 512 32768 1073741824 1152921504606846976 9 81 729 59049 3486784401 12157665459056928801 10 100 1000 100000 10000000000 100000000000000000000
一种可能的解决方案是更改制表符宽度,但第一列已经比需要的大。最佳解决方案是独立设置每列的宽度。正如你现在可能已经猜到的那样,字符串格式提供了解决方案
运行此版本会生成以下输出
i i**2 i**3 i**5 i**10 i**20 1 1 1 1 1 1 2 4 8 32 1024 1048576 3 9 27 243 59049 3486784401 4 16 64 1024 1048576 1099511627776 5 25 125 3125 9765625 95367431640625 6 36 216 7776 60466176 3656158440062976 7 49 343 16807 282475249 79792266297612001 8 64 512 32768 1073741824 1152921504606846976 9 81 729 59049 3486784401 12157665459056928801 10 100 1000 100000 10000000000 100000000000000000000
len-在每次%中的转换规范表示左对齐。数值指定最小长度,因此%-13d是一个至少 13 个字符宽的左对齐数字。
本章介绍了许多新概念。以下摘要和练习集可能有助于您记住所学内容
练习
将 Python 解释器对以下每个表达式的求值写出来
- >>> 'Python'[1]
- >>> "Strings are sequences of characters."[5]
- >>> len("wonderful")
- >>> 'Mystery'[:4]
- >>> 'p' in 'Pinapple'
- >>> 'apple' in 'Pinapple'
- >>> 'pear' in 'Pinapple'
- >>> 'apple' > 'pinapple'
- >>> 'pinapple' < 'Peach'
- 编写 Python 代码使以下每个 doctest 通过
- *
- *
- *
修改
prefixes = "JKLMNOPQ"
suffix = "ack"
for letter in prefixes:
print letter + suffix
以便正确拼写 Ouack 和 Quack。
将
fruit = "banana"
count = 0
for char in fruit:
if char == 'a':
count += 1
print count
封装在一个名为 count_letters
的函数中,并使其通用,使其接受字符串和字母作为参数。
现在重写 count_letters 函数,以便它不是遍历字符串,而是重复调用 find(可选参数的版本),使用可选的第三个参数来定位要计数的字母的新出现位置。
你认为哪个版本的is_lower会更快?除了速度之外,你还能想到其他理由来偏爱其中一个版本吗?
创建一个名为stringtools.py的文件并将以下内容放入其中
向reverse添加一个函数体,以使 doctest 通过。添加mirror到stringtools.py .
为其编写一个函数体,使其能够像 doctest 所示那样工作。包含remove_letterinstringtools.py .
为其编写一个函数体,使其能够像 doctest 所示那样工作。最后,一次添加以下每个函数的函数体,
直到所有 doctest 都通过。在 Python shell 中尝试以下每个格式化字符串操作并记录结果
- "%s %d %f" % (5, 5, 5)
- "%-.2f" % 3
- "%-10.2f%-10.2f" % (7, 1.0/2)
- print " $%5.2fn $%5.2fn $%5.2f" % (3, 4.5, 11.2)
以下格式化字符串存在错误。修正它们
- "%s %s %s %s" % ('this', 'that', 'something')
- "%s %s %s" % ('yes', 'no', 'up', 'down')
- "%d %f %f" % (3, 3, 'three')