PYTHON — 字符串
字符串不像整数、浮点数和布尔型。字符串是一个 序列(sequence) ,这就意味着 它是其他值的一个有序的集合。在这章中,你将学习怎么去访问字符串里的字符, 同时你也会学习到字符串提供的一些方法。
1.字符串是一个序列
字符串是由字符组成的序列。 你可以用括号运算符一次访问一个字符:
>>> fruit = 'banana'
>>> letter = fruit[1]
第2条语句从
fruit
中选择索引为1的字符并将它赋给
letter
。
括号中的表达式被称作 索引(index) 。索引指出在序列中你想要哪个字符(因此而得名)。
但是你可能不会获得你期望的东西:
>>> letter
'a'
对于大多数人,
'banana'
的第一个字母是b而不是a。 但是对于计算机科学家,索引是从字符串起点开始的位移量F(offset),第一个字母的位移量就是0。
>>> letter = fruit[0]
>>> letter
'b'
所以b是
'banana'
的第0个字母(“zero-eth”), a是第一个字母(“one-eth”),n是第二个字 母(“two-eth”)。
你可以使用一个包含变量名和运算符的表达式作为索引:
>>> i = 1
>>> fruit[i]
'a'
>>> fruit[i+1]
'n'
索引值必须使用整数。 否则你会得到:
>>> letter = fruit[1.5]
TypeError: string indices must be integers
2.len
len
是一个内建函数,其返回字符串中的字符数量:
>>> fruit = 'banana'
>>> len(fruit)
6
为了获得一个字符串的最后一个字符,你可能会尝试像这样操作:
>>> length = len(fruit)
>>> last = fruit[length]
IndexError: string index out of range
出现IndexError的原因,是在
'banana'
中没有索引为6的字母。 由于我们从0开始计数,六个字母的编号是0到5。 为了获得最后一个字符,你必须将
length
减一:
>>> last = fruit[length-1]
>>> last
'a'
或者你可以使用负索引,即从字符串的结尾往后数。表达式
fruit[-1]
返回最后一个字母,
fruit[-2]
返回倒数第二个字母, 以此类推。
3.使用for循环遍历
许多计算中需要一个字符一个字符地处理字符串。 通常计算从字符串的头部开始,依次选择每个字符,对其做一些处理, 然后继续直到结束。 这种处理模式被称作
遍历(traversal)
。 编写遍历的方法之一是使用 while循环:
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index = index + 1
该循环遍历字符串并在每行显示一个字符串。该循环的条件是
index < len(fruit)
, 所以当
index
和字符串的长度相等时, 条件为假, 循环体不被执行。 被访问的最后一个字符的索 引为
len(fruit)-1
, 这也是字符串的最后一个字符。
我们做个练习,编写一个函数,接受一个字符串作为实参, 按照从后向前的顺序显示字符,每行只显示一个。
编写遍历的另一种方法是使用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”拼写错了。我们做个练习, 修改这 个程序,解决这个问题。
4.字符串切片
字符串的一个片段被称作
切片(slice)
。 选择一个切片的操作类似于选择一个字符:
>>> s = 'Monty Python'
>>> s[0:5]
'Monty'
>>> s[6:12]
'Python'
[n:m]
操作符返回从第n个字符到第m个字符的字符串片段,包括第一个,但是不包括最后一个。 这个行为违反直觉,但是将指向两个字符之间的索引, 想象成 图8-1:切片索引中那样或许有帮助。
图8-1:切片索引
如果你省略第一个索引(冒号前面的值),切片起始于字符串头部。 如果你省略第二个索引,切片一直 到字符串结尾:
>>> fruit = 'banana'
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'
如果第一个索引大于或等于第二个,结果是
空字符串(empty string)
, 用两个引号表示:
>>> fruit = 'banana'
>>> fruit[3:3]
''
一个空字符串不包括字符而且长度为0,但除此之外, 它和其它任何字符串一样。
继续这个例子, 你认为
fruit[:]
的结果是什么?尝试运行看看。
5.字符串是不可变的
你会很想在赋值语句的左边使用
[]
, 来改变字符串的一个字符。 例如:
>>> greeting = 'Hello, world!'
>>> greeting[0] = 'J'
TypeError: 'str' object does not support item assignment
错误信息中的“object(对象)”是那个字符串,“item(元素)”是你要赋值的字符。目前,我们认为 对象(object)和值是同一样的东西,但是我们后面将改进此定义(详见“对象与值”一节)。
出现此错误的原因是字符串是
不可变的(immutable)
,这意味着你不能改变一个已存在的字符串。 你最多只能创建一个新的字符串,在原有字符串的基础上略有变化:
>>> greeting = 'Hello, world!'
>>> new_greeting = 'J' + greeting[1:]
>>> new_greeting
'Jello, world!'
上面的示例中,我们将一个新的首字母拼接到
greeting
的一个切片上。它不影响原字符串。
6.搜索
下面的函数起什么作用?
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
。
这种计算模式——遍历一个序列并在找到寻找的东西时返回——被称作 搜索(search) 。
我们做个练习,修改 ``find``函数使得它接受第三个参数,即从何处开始搜索的索引。
7.循环和计数
下面的程序计算字母a在字符串中出现的次数:
word = 'banana'
count = 0
for letter in word:
if letter == 'a':
count = count + 1
print(count)
此程序演示了另一种被称作**计数器(counter)**的计算模式。变量
count
初始化为0,然后每次出现a时递增。当循环结束时,
count
包含了字母a出现的总次数。
我们做一个练习,将这段代码封装在一个名为
count
的函数中,并泛化该函数,使其接受字符串和字母作为实参。
然后重写这个函数,不再使用字符串遍历,而是使用上一节中三参数版本的
find
函数。
8.字符串方法
字符串提供了可执行多种有用操作的
方法(method)
。方法和函数类似,接受实参并返回一个值,但是语法不同。 例如,
upper
方法接受一个字符串,并返回一个都是大写字母的新字符串。
不过使用的不是函数语法
upper(word)
, 而是方法的语法
word.upper()
。
>>> word = 'banana'
>>> new_word = word.upper()
>>> new_word
'BANANA'
点标记法的形式指出方法的名字,
upper
,以及应用该方法的字符串的名字,
word
。 空括号表明该方法不接受实参。
这被称作
方法调用(invocation)
;在此例中, 我们可以说是在
word
上调用
upper
。
事实上,有一个被称为
find
的字符串方法, 与我们之前写的函数极其相似:
>>> word = 'banana'
>>> index = word.find('a')
>>> index
1
此例中,我们在
word
上调用
find
,并将我们要找的字母作为参数传入。
事实上,
find
方法比我们的函数更通用;它还可以查找子字符串,而不仅仅是字符:
>>> word.find('na')
2
find
默认从字符串的首字母开始查找, 它还可以接受第二个实参,即从何处开始的索引。
>>> word.find('na', 3)
4
这是一个
可选参数(optional argument)
的例子;
find
也可以接受结束查找的索引作为第三个实参:
>>> name = 'bob'
>>> name.find('b', 1, 2)
-1
此次搜索失败,因为
'b'
没有出现在索引1-2之间(不包括2)。 一直搜索到第二个索引,但是并不搜索第二个索引, 这使得
find
跟切片运算符的行为一致.
http:// 9.in 运算符
单词
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代码有时候读起来像是自然语言。你可以这样读此循环,“对于(每个) 在(第一个)单词中的字母,如果(该)字母(出现)在(第二个)单词中,打印(该)字母”。
如果你比较
'apples'
和
'oranges'
,你会得到下面的结果:
>>> in_both('apples', 'oranges')
a
e
s
10.字符串比较
关系运算符也适用于字符串。可以这样检查两个字符串是否相等:
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.
解决此问题的常见方式是,在执行比较之前,将字符串转化为标准格式,例如都是小写字母。请牢记这点, 万一你不得不防卫一名手持菠萝男子的袭击呢。
11.调试
当你使用索引遍历序列中的值时,正确地指定遍历的起始和结束点有点困难。下面是一个用来比较两个单词的函数,如果一个单词是另一个的倒序,则返回
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
。否则,在函数其余的部分,我们可以假定单词是等长的。这是
检查类型
一节中提到的监护人模式的一个例子。
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) # 这里添加打印语句
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, 超出字符串
'post'
的范围了。最后一个字符的索引是3,所以
j
的初始值应该是
len(word2)-1
。
如果我解决了这个错误,然后运行程序, 将获得如下输出:
>>> is_reverse('pots', 'stop')
0 3
1 2
2 1
True
这次我们获得了正确的答案,但是看起来循环只运行了三次,这很奇怪。画栈图可以帮我们更好的理解发生了什么。在第一次迭代期间,
is_reverse
的栈帧如图8-2:堆栈图所示。
图8-2:堆栈图
我对堆栈图做了些调整,重新排列了栈帧中的变量,增加了虚线来说明
i
和
j
的值表示
word1
和
word2
中的字符。
从这个堆栈图开始,在纸上运行程序,每次迭代时修改
i
和
j
的值。查找并解决这个函数的中第二个错误。
12.术语表
对象(object):变量可以引用的东西。现在你将对象和值等价使用。
序列(sequence):一个有序的值的集合,每个值通过一个整数索引标识。
元素(item):序列中的一个值。
索引(index):用来选择序列中元素(如字符串中的字符)的一个整数值。在Python中,索引从0开始。
切片(slice):以索引范围指定的字符串片段。
空字符串(empty string):一个没有字符的字符串,长度为0,用两个引号表示。
不可变 (immutable):元素不能被改变的序列的性质。
遍历(traversal):对一个序列的所有元素进行迭代, 对每一元素执行类似操作。
搜索(search):一种遍历模式,当找到搜索目标时就停止。
计数器(counter):用来计数的变量,通常初始化为0,并以此递增。
方法调用(invocation): 执行一个方法的声明.可选参数(optional argument)一个函数或者一个方法中不必要指定的参数。