跳转到内容

像计算机科学家一样思考:用 Python 学习 第 2 版/字符串

来自 Wikibooks,开放世界中的开放书籍

字符串

[编辑 | 编辑源代码]

7.1 复合数据类型

[编辑 | 编辑源代码]

到目前为止,我们已经看到了五种类型int, float, bool, NoneTypestr. 字符串在本质上不同于其他四种类型,因为它们是由更小的片段 --- **字符**组成的。

包含更小片段的类型称为 **复合数据类型**。根据我们正在做什么,我们可能希望将复合数据类型视为一个整体,或者我们可能希望访问它的各个部分。这种模糊性很有用。

**方括号运算符**从字符串中选择单个字符

>>> 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]生成倒数第二个字母,以此类推。

7.3 遍历和for循环

[编辑 | 编辑源代码]

许多计算都涉及逐个字符地处理字符串。它们通常从开头开始,依次选择每个字符,对其执行某些操作,并继续直到结束。这种处理模式称为 **遍历**。编码遍历的一种方法是使用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 拼写错误。你将在下面的练习中修复这个问题。

7.4 字符串切片

[编辑 | 编辑源代码]

字符串的子字符串称为 **切片**。选择切片类似于选择字符

>>> 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运算符

[编辑 | 编辑源代码]

lenin运算符测试一个字符串是否是另一个字符串的子字符串

请注意,一个字符串是它自身的子字符串

in运算符与使用+的字符串连接结合起来,我们可以编写一个函数,从字符串中删除所有元音

测试此函数以确认它是否按预期执行。

7.8 A查找函数

[编辑 | 编辑源代码]

以下函数的功能是什么?

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` 中介绍的计数模式的另一个示例。

7.10 可选参数

[编辑 | 编辑源代码]

为了找到字符在字符串中第二次或第三次出现的位数,我们可以修改查找函数,添加第三个参数用于搜索字符串中的起始位置。

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字符串模块

[编辑 | 编辑源代码]

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 未出现在索引范围从12(不包括2).

字符分类

[编辑 | 编辑源代码]

检查字符并测试其是否为大写或小写,或者其是否为字符或数字通常很有帮助。字符串模块提供了几个常量,这些常量对于这些目的非常有用。其中之一,string.digits,我们已经见过。

字符串string.lowercase包含系统认为是小写的字母。类似地,string.uppercase包含所有大写字母。尝试以下操作并查看你得到的结果。

我们可以使用这些常量和查找对字符进行分类。例如,如果find(lowercase, ch)返回的值不是-1,那么ch一定是小写字母。

或者,我们可以利用in运算符

作为另一种选择,我们可以使用比较运算符

如果ch介于 az 之间,那么它一定是小写字母。

字符串模块中定义的另一个常量可能会让你感到惊讶,当你打印它时,

空白字符移动光标而不打印任何内容。它们在可见字符之间创建空白(至少在白纸上)。常量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表示该值是一个十进制整数。

在第三个示例中,变量n1n2的整数值分别为45。格式字符串中有四个转换说明:三个%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 个字符宽的左对齐数字。

总结和第一个练习

[编辑 | 编辑源代码]

本章介绍了许多新概念。以下摘要和练习集可能有助于您记住所学内容

练习

  1. 将 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'
  2. 编写 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 通过。
  • 添加mirrorstringtools.py .

    为其编写一个函数体,使其能够像 doctest 所示那样工作。
  • 包含remove_letterinstringtools.py .

    为其编写一个函数体,使其能够像 doctest 所示那样工作。
  • 最后,一次添加以下每个函数的函数体,

    直到所有 doctest 都通过。
  • 在 Python shell 中尝试以下每个格式化字符串操作并记录结果

    1. "%s %d %f" % (5, 5, 5)
    2. "%-.2f" % 3
    3. "%-10.2f%-10.2f" % (7, 1.0/2)
    4. print " $%5.2fn $%5.2fn $%5.2f" % (3, 4.5, 11.2)
  • 以下格式化字符串存在错误。修正它们

    1. "%s %s %s %s" % ('this', 'that', 'something')
    2. "%s %s %s" % ('yes', 'no', 'up', 'down')
    3. "%d %f %f" % (3, 3, 'three')
华夏公益教科书