Think Python/字符串
字符串是序列字符。你可以使用括号运算符一次访问一个字符
>>> fruit = 'banana'
>>> letter = fruit[1]
第二个语句从fruit
中选择第1个字符并将其赋值给letter
。
括号中的表达式称为索引。索引指示您想要序列中的哪个字符(因此得名)。
但是您可能不会得到您期望的结果
>>> print letter
a
对大多数人来说,'banana'
的第一个字母是b
,而不是a
。但对于计算机科学家来说,索引是字符串开头的偏移量,第一个字母的偏移量为零。
>>> letter = fruit[0]
>>> print letter
b
所以b
是'banana'
的第0个字母(“零次”),a
是第1个字母(“一次”),n
是第2个字母(“二次”)。
您可以使用任何表达式(包括变量和运算符)作为索引,但索引的值必须是整数。否则你会得到
>>> letter = fruit[1.5]
TypeError: string indices must be integers
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]
生成倒数第二个字母,依此类推。
许多计算涉及逐个字符处理字符串。它们通常从开头开始,依次选择每个字符,对其进行处理,并继续到结尾。这种处理模式称为遍历。使用while
循环是编写遍历的一种方法
index = 0
while index < len(fruit):
letter = fruit[index]
print letter
index = index + 1
此循环遍历字符串并在单独的行上显示每个字母。循环条件是index < len(fruit)
,所以当index
等于字符串的长度时,条件为假,并且不执行循环体。访问的最后一个字符是索引为len(fruit)-1
的字符,它是字符串中的最后一个字符。
使用for
循环是编写遍历的另一种方法
for char in fruit:
print char
每次循环时,字符串中的下一个字符都会被分配给变量char
。循环继续,直到没有字符剩余。
以下示例展示了如何使用连接(字符串加法)和for
循环生成一个按字母顺序排列的字母表系列(即按字母顺序排列)。在罗伯特·麦克洛斯基的书《为小鸭子让路》中,小鸭子的名字是杰克、凯克、莱克、麦克、纳克、奥克、帕克和夸克。此循环按顺序输出这些名称
prefixes = 'JKLMNOPQ'
suffix = 'ack'
for letter in prefixes:
print letter + suffix
输出为
Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack
当然,这不太正确,因为“Ouack”和“Quack”拼写错误。
修改程序以修复此错误。
字符串的一部分称为切片。选择切片类似于选择字符
>>> s = 'Monty Python'
>>> print s[0:5]
Monty
>>> print s[6:13]
Python
运算符[n:m]
返回字符串从“第n个”字符到“第m个”字符的部分,包括第一个但不包括最后一个。这种行为违反直觉,但想象索引指向字符之间可能会有所帮助,如下面的图所示
如果您省略第一个索引(冒号之前),则切片从字符串开头开始。如果您省略第二个索引,则切片将一直到字符串结尾
>>> fruit = 'banana'
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'
如果第一个索引大于或等于第二个索引,则结果为空字符串,用两个引号表示
>>> fruit = 'banana'
>>> fruit[3:3]
空字符串不包含任何字符,长度为0,但除此之外,它与任何其他字符串相同。
假设'fruit
'是一个字符串,'fruit[:]
'是什么意思?
您可能会很想在赋值的左侧使用[]
运算符,目的是更改字符串中的字符。例如
>>> greeting = 'Hello, world!'
>>> greeting[0] = 'J'
TypeError: object does not support item assignment
在这种情况下,“对象”是字符串,“项目”是您尝试分配的字符。目前,“对象”与值相同,但稍后我们将完善该定义。“项目”是序列中的一个值。
发生错误的原因是字符串是不可变的,这意味着您无法更改现有字符串。您能做的最好的事就是创建一个新的字符串,它是原始字符串的变化形式
>>> greeting = 'Hello, world!'
>>> new_greeting = 'J' + greeting[1:]
>>> print new_greeting
Jello, world!
此示例将一个新的第一个字母连接到greeting
的切片上。它对原始字符串没有影响。
以下函数有什么作用?
def find(word, letter):
index = 0
while index < len(word):
if word[index] == letter:
return index
index = index + 1
return -1
在某种意义上,find
与[]
运算符相反。它不是接受一个索引并提取相应的字符,而是接受一个字符并查找该字符出现的索引。如果字符未找到,该函数返回-1
。
这是我们见到的第一个在循环内使用return
语句的示例。如果word[index] == letter
,则函数会退出循环并立即返回。
如果字符没有出现在字符串中,则程序会正常退出循环并返回-1
。
这种计算模式——遍历一个序列并在找到我们正在寻找的东西时返回——被称为搜索。
修改 'find
' 使其具有第三个参数,即 'word
' 中应开始查找的索引。
以下程序计算字母 a
在字符串中出现的次数。
word = 'banana'
count = 0
for letter in word:
if letter == 'a':
count = count + 1
print count
该程序演示了另一种称为计数器的计算模式。变量 count
初始化为 0,然后每次找到一个 a
时就增加 1。当循环退出时,count
包含结果——a
的总数。
count
' 的函数中,并将其泛化,使其接受字符串和字母作为参数。find
' 的三参数版本。方法类似于函数——它接收参数并返回一个值——但语法不同。例如,方法 upper
接收一个字符串并返回一个所有字母都大写的新字符串。
它不使用函数语法 upper(word)
,而是使用方法语法 word.upper()
。
>>> word = 'banana'
>>> new_word = word.upper()
>>> print new_word
BANANA
这种点符号形式指定了方法的名称 upper
和要应用方法的字符串的名称 word
。空括号表示此方法不接收任何参数。
方法调用称为调用;在这种情况下,我们可以说我们正在对 word
调用 upper
。
事实证明,存在一个名为 find
的字符串方法,它与我们编写的函数非常相似。
>>> word = 'banana'
>>> index = word.find('a')
>>> print index
1
在此示例中,我们对 word
调用 find
并将我们正在寻找的字母作为参数传递。
实际上,find
方法比我们的函数更通用;它可以查找子字符串,而不仅仅是字符。
>>> word.find('na')
2
它可以作为第二个参数接收它应该开始查找的索引。
>>> word.find('na', 3)
4
以及作为第三个参数接收它应该停止查找的索引。
>>> name = 'bob'
>>> name.find('b', 1, 2)
-1
此搜索失败,因为 b
未出现在从 1
到 2
(不包括 2
)的索引范围内。
count
' 的字符串方法,它类似于上一个练习中的函数。阅读此方法的文档并编写一个调用,该调用计算 banana
中 'a
' 的数量。单词 in
是一个布尔运算符,它接收两个字符串,如果第一个字符串作为子字符串出现在第二个字符串中,则返回 True
。
>>> 'a' in 'banana'
True
>>> 'seed' in 'banana'
False
例如,以下函数打印来自 word1
的所有也出现在 word2
中的字母。
def in_both(word1, word2):
for letter in word1:
if letter in word2:
print letter
使用精心选择的变量名,Python 有时读起来像英语。您可以阅读此循环,“对于 (每个) 字母在 (第一个) 单词中,如果 (该) 字母 (出现在) (第二个) 单词中,则打印 (该) 字母。”
以下是在比较苹果和橘子时得到的结果。
>>> in_both('apples', 'oranges')
a
e
s
比较运算符适用于字符串。要查看两个字符串是否相等。
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.
解决此问题的常用方法是在执行比较之前将字符串转换为标准格式,例如全部小写。如果您必须为自己辩护以对抗拿着菠萝的男子,请牢记这一点。
当您使用索引遍历序列中的值时,很难正确地确定遍历的开始和结束。以下函数应该比较两个单词,如果其中一个单词是另一个单词的反转,则返回 True
,但它包含两个错误。
def is_reverse(word1, word2):
if len(word1) != len(word2):
return False
i = 0
j = len(word2)
while j > 0:
if word1[i] != word2[j]:
return False
i = i+1
j = j-1
return True
第一个 if
语句检查单词的长度是否相同。如果不是,我们可以立即返回 False
,然后,对于函数的其余部分,我们可以假设单词的长度相同。这是第 6.8 节中的守护模式的一个示例。
i
和 j
是索引:i
正向遍历 word1
,而 j
反向遍历 word2
。如果我们找到两个不匹配的字母,我们可以立即返回 False
。如果我们遍历整个循环并且所有字母都匹配,则返回 True
。
如果我们用单词“pots”和“stop”测试此函数,我们期望返回值 True
,但我们得到一个 IndexError。
>>> is_reverse('pots', 'stop')
...
File "reverse.py", line 15, in is_reverse
if word1[i] != word2[j]:
IndexError: string index out of range
为了调试这种错误,我的第一步是在错误出现的行之前立即打印索引的值。
while j > 0:
print i, j # print here
if word1[i] != word2[j]:
return False
i = i+1
j = j-1
现在,当我再次运行程序时,我获得了更多信息。
>>> is_reverse('pots', 'stop')
0 4
...
IndexError: string index out of range
第一次遍历循环时,j
的值为 4,这对于字符串 'pots'
来说超出了范围。最后一个字符的索引是 3,因此 j
的初始值应该是 len(word2)-1
。
如果我修复了该错误并再次运行程序,我得到。
>>> is_reverse('pots', 'stop')
0 3
1 2
2 1
True
这次我们得到了正确的答案,但看起来循环只运行了三次,这很可疑。为了更好地了解正在发生的事情,绘制状态图很有用。在第一次迭代期间,is_reverse
的帧如下所示。
我通过排列帧中的变量并添加虚线来稍微扩展了一点,以显示 i
和 j
的值指示 word1
和 word2
中的字符。
i
' 和 'j
' 的值。找到并修复此函数中的第二个错误。- 对象
- 变量可以引用的东西。目前,您可以互换使用“对象”和“值”。
- 序列
- 一个有序集合;也就是说,一组值,其中每个值都由一个整型索引标识。
- 项目
- 序列中的一个值。
- 索引
- 用于选择序列中项目的整型值,例如字符串中的字符。
- 切片
- 由一组索引指定的部分字符串。
- 空字符串
- 没有字符且长度为 0 的字符串,用两个引号表示。
- 不可变
- 无法分配其项目的序列的属性。
- 遍历
- 迭代序列中的项目,对每个项目执行类似的操作。
- 搜索
- 一种遍历模式,它在找到正在寻找的东西时停止。
- 计数器
- 用于统计某物的变量,通常初始化为零,然后递增。
- 方法
- 与对象相关联的函数,使用点符号调用。
- 调用
- 调用方法的语句。
字符串切片可以接受第三个索引,该索引指定“步长”;也就是说,连续字符之间的空格数。
步长为 2 表示隔一个字符;3 表示隔两个字符,依此类推。
>>> fruit = 'banana'
>>> fruit[0:5:2]
'bnn'
步长为 -1 表示向后遍历单词,因此切片 [::-1]
生成反转的字符串。
使用此习惯用法编写 is_palindrome
的单行版本,来自练习 '6.6'。
阅读 'docs.python.org/lib/string-methods.html
' 中字符串方法的文档。您可能需要尝试其中的一些方法,以确保您了解其工作原理。'strip
' 和 'replace
' 特别有用。
文档使用了一种可能令人困惑的语法。例如,在 find(sub[, start[, end]])
中,方括号表示可选参数。所以 'sub
' 是必需的,但 'start
' 是可选的,如果您包含 'start
',那么 'end
' 是可选的。
以下函数都旨在检查字符串是否包含任何小写字母,但至少其中一些是错误的。对于每个函数,描述函数的实际功能。
def any_lowercase1(s):
for c in s:
if c.islower():
return True
else:
return False
def any_lowercase2(s):
for c in s:
if 'c'.islower():
return 'True'
else:
return 'False'
def any_lowercase3(s):
for c in s:
flag = c.islower()
return flag
def any_lowercase4(s):
flag = False
for c in s:
flag = flag or c.islower()
return flag
def any_lowercase5(s):
for c in s:
if not c.islower():
return False
return True
ROT13 是一种弱加密形式,它涉及将单词中的每个字母“旋转”13 个位置[1]。旋转字母意味着在字母表中移动它,必要时环绕到开头,因此 'A' 向右移 3 个位置变为 'D','Z' 向右移 1 个位置变为 'A'。
编写一个名为rotate_word
的函数,该函数以字符串和整数为参数,并返回一个新字符串,该字符串包含原始字符串中“旋转”指定数量的字母。
例如,“cheer” 旋转 7 个位置变为 “jolly”,而 “melon” 旋转 -10 个位置变为 “cubed”。
你可能想要使用内置函数 'ord
'(将字符转换为数字代码)和 'chr
'(将数字代码转换为字符)。
互联网上可能冒犯性的笑话有时会用 ROT13 编码。如果你不容易被冒犯,请找到并解码一些这样的笑话。
- ↑ 参见
wikipedia.org/wiki/ROT13