第6章 字符串
6.1 字符串是字符的序列
字符串是若干字符的序列。你可以用方括号运算符逐一访问每个字符:
>>> fruit = 'banana'
>>> letter = fruit[1]
第二条语句从fruit变量中提取索引位置为1的字符,并把它赋予letter变量。
方括号里的表达式称为索引。索引可以指向字符序列中你想要的字符,作用如其名。
但是这并不是你想要的结果:
>>> print letter
a
大多数人认为'banana'的第一个字符是b,而不是a。但在Python中,索引是从字符串头部算起的一个偏移量,第一个字母的偏移量为0。
>>> letter = fruit[0]
>>> print letter
b
因此,b是'banana'的第零个字母,a是第一个字母,n是第二个字母。
索引可以是任何表达式,包括变量与运算符在内。但是,索引的值必须是整数,否则会得到如下错误信息:
>>> letter = fruit[1.5]
TypeError: string indices must be integers
6.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]是倒数第二个字母,以此类推。
6.3 通过循环遍历字符串
按照一次一个字符的方式处理一个字符串需要花费大量的计算。通常从字符串头部开始,逐个选择每个字符,对其进行操作,然后继续下一个,直到结束。这种处理模式称为遍历(traversal)。遍历的一种写法是使用while循环:
index = 0
while index < len(fruit):
letter = fruit[index]
print letter
index = index + 1
这个循环遍历了整个字符串,每行显示一个字母。循环条件是index < len(fruit)
,当index等于字符串长度时,循环条件为假,那么语句体就不会被执行。字符串最后一个字符的索引是len(fruit)-1
。
习题6.1 编写一个while循环,从字符串的最后一个字符开始,反向逐一处理,直到字符串的第一个字符,一行显示一个字母。
遍历的另一种写法是用for循环:
for char in fruit:
print char
每一次循环中字符串的下一个字符赋值给char变量。继续循环下去,直到没有字符了。
6.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。除此之外,它和其它字符串没有差别。
习题6.2 假设fruit是一个字符串,那么fruit[:]表示什么?
6.5 字符串是不可变的
在赋值语句的左边使用[]运算符,尝试改变字符串中的字符。举例如下:
>>> 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的切片连接在一起。这不会对原先的字符串造成影响。
6.6 循环与统计
下面的程序统计了字母a在字符串中出现的次数:
word = 'banana'
count = 0
for letter in word:
if letter == 'a':
count = count + 1
print count
这个程序演示了另一种计算模式,称为计数器。变量count初始值为0,每当发现一个a,count值就加一。当循环停止时,count就得到了结果,即a出现的总次数。
习题6.3 定义一个count函数并封装这段代码,对其进行通用化改造,能够接收字符串和字母作为参数。
6.7 in运算符
单词in是一个布尔运算符,对两个字符串进行比较,如果第一个字符串是第二个字符串的子串,则返回True。
>>> 'a' in 'banana'
True
>>> 'seed' in 'banana'
False
6.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.
解决这个问题的常见方法是将字符串转换成一种标准格式,例如,在比较之前都转化为小写。
6.9 字符串方法
字符串是一种Python对象。一个对象包括数据(即字符串本身)和方法。这些方法是内置在对象中的有效函数,可以作用于对象的任一实例。
Python有一个dir函数,它可以列出对象所有可用的方法。type函数显示对象的类型,dir函数显示的是对象可用的方法。
>>> stuff = 'Hello world'
>>> type(stuff)
<type 'str'>
>>> dir(stuff)
['capitalize', 'center', 'count', 'decode', 'encode',
'endswith', 'expandtabs', 'find', 'format', 'index',
'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace',
'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip',
'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() -> string
Return a copy of the string S with only its first character
capitalized.
>>>
当dir函数列出这些方法,你就可以用help获取关于这些方法的文档。有关字符串方法比较全面的文档详见http://docs.python.org/library/string.html。
调用方法与调用函数类似,都是传入参数,返回结果,但它们的语法是不同的。调用方法的语法是,使用句点作为分隔,在变量名后面跟上方法名。
例如,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
find方法还可以设置第二个参数,从哪个索引位置开始查找:
>>> word.find('na', 3)
4
一个常见任务是利用strip方法移除字符串首尾的空白(包括空格、制表符和换行符)。
>>> line = ' Here we go '
>>> line.strip()
'Here we go'
像startswith这样的方法返回的是布尔值。
>>> line = 'Please have a nice day'
>>> line.startswith('Please')
True
>>> line.startswith('p')
你会注意到,startswith方法对大小写敏感,在检查之前,使用lower方法将其全部转换为小写字母。
>>> line = 'Please have a nice day'
>>> line.startswith('p')
False
>>> line.lower()
'please have a nice day'
>>> line.lower().startswith('p')
True
最后一个例子中调用了lower方法,然后用startswith方法检查小写转换后的字符串是否以字母p开头。只要留意次序,我们就可以在一个表达式上运用多种方法。
习题6.4 字符串方法count与之前练习过的函数相似。请访问http://docs.python.org/library/string.html,查看这个方法的文档,编写一个方法调用,统计a在'banana'中出现的次数。
6.10 字符串解析
通常,我们想要在一个字符串中寻找它的子串。如下是一行结构化的字符串:
From stephen.marquard@ uct.ac.za Sat Jan 5 09:14:16 2008
我们只想抽出电子邮件的第二部分(即uct.ac.za),可以通过find方法和字符串切片来实现。
首先,在字符串中找到@符号的位置。其次,找到@符号之后第一个空格所在的位置。最后,再用字符串切片来提取字符串中我们需要的部分。
>>> data = 'From [email protected] 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方法从何处开始寻找。当切分字符串时,我们提取的字符是“位于@符号之后但不包括空格”的字符。
有关find方法的文档,请访问http://docs.python.org/library/string.html。
6.11 格式操作符
格式操作符%可以构建字符串,使用变量中存储的数据来替代字符串的一部分。对整数而言,%是模运算符。如果第一个操作对象是字符串,那么%就是格式操作符。
第一个操作对象是格式字符串,它包含一个或多个格式化序列,用来指定第二个操作对象的格式。最终处理结果是字符串。
例如,格式序列'%d'表示第二个操作对象会被格式化为整数型(d表示十进制):
>>> camels = 42
>>> '%d' % camels
'42'
运行的结果是字符串'42',不要与整数42搞混了。
格式序列可以出现在字符串中的任意位置,所以你可以在一个语句中嵌入一个值:
>>> camels = 42
>>> 'I have spotted %d camels.' % camels
'I have spotted 42 camels.'
如果字符串中存在多个格式序列,那么第二个参数必须是元组。每个格式序列与元组的元素依次对应。
下面的例子使用'%d'格式化整数,'%g'格式化浮点数(不要问为什么),'%s'格式化字符串:
>>> 'In %d years I have spotted %g %s.' % (3, 0.1, 'camels')
元组中元素的数量必须与字符串中的格式序列数量一致。另外,元素的类型也要与格式序列匹配:
>>> '%d %d %d' % (1, 2)
TypeError: not enough arguments for format string
>>> '%d' % 'dollars'
TypeError: illegal argument type for built-in operation
第一个例子中元素的数量不够,第二个例子中元素的格式是错的。
格式运算符很强大,但不那么容易上手。如需了解更多,请访问http://docs.python.org/lib/typesseq-strings.html。
6.12 调试
编程时经常问问自己, “这里可能出现什么样的错误?”或者“我们的用户可能会做怎样疯狂的事情使(看似)完美的程序崩溃?”。这是需要长期培养的编程意识。
例如,第5章迭代中介绍while循环的程序示例如下:
while True:
line = raw_input('> ')
if line[0] == '#' :
continue
if line == 'done':
break
print line
print 'Done!'
当用户输入一个空行会发生什么:
> 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] == '#' :
输入空行之前代码运行正常。由于没有第0位字符,我们得到了异常信息反馈。两种方法可以解决这个问题,即使这一行为空,仍然能保证“安全”运行。
一种方法是使用startswith方法,如果字符串为空就返回False。
另一种方法是使用守护模式,通过一条if语句进行控制,保证第二个逻辑表达式只有在字符串中至少有一个字符时进行判断。
if len(line) > 0 and line[0] == '#' :
6.13 术语
计数器:用来统计的变量。通常初始化为零,然后累增。
空字符串:不包含字符、长度为零的字符串,用两个引号表示。
格式操作符:%操作符对格式字符串和元组进行操作,根据特定格式字符串,对元组元素进行格式化后生成一个字符串。
格式序列:格式字符串中的字符序列,例如,%d,它表示一个值应该如何进行格式化。
格式字符串:使用了格式操作符的字符串,它包含了格式序列。
标记:用来表示条件是否为真的布尔变量。
调用:方法的召唤语句。
不可变:序列的一种属性,序列中的数据项不能被赋值。
索引:用来选择序列中数据项的一个整数值,例如,表示字符串中一个字符的位置。
数据项:序列中的一个值。
方法:与对象相关的函数,使用句点来调用。
对象:变量可以引用的东西。现在,你可以交替使用“对象”或者“值”。
搜索:找到所要找的内容才会停止的一种遍历模式。
序列:一个有序集合,集合中的每个值通过一个整数索引定位。
切片:根据索引区间指定字符串的一部分。
遍历:遍历序列中的数据项,对每个数据项执行类似的操作。
6.14 练习
习题6.5 使用以下语句存储一个字符串:
str = ’X-DSPAM-Confidence: 0.8475’
使用find方法和字符串切片,提取出字符串中冒号后面的部分,然后使用float函数,将提取出来的字符串转换为浮点数。
习题6.6 访问http://docs.python.org/lib/string-methods.html,阅读字符串方法的文档。 你可能想要拿它们操作一下,以便理解它们的工作原理。字符串方法中的strip和replace特别有用。
文档中使用的语法可能会使人困惑。例如,在find(sub[, start[, end]])中,方括号表示可选的参数。sub是必需的,start是可选的;如果包含了start,那么end就是可选的。