3.3 字符和字符串
观察计算机的键盘,上面每个键对应着一个“字符”(character)。注意,字符不仅是字母,通常包括字母、数字、标点符号和一些控制符等。当计算机能够处理字符后,它就不再单纯“计算”了,而是具有了“电脑”的功能。
3.3.1 字符编码
如前所述,计算机只能认识二进制数字,所有提交给它的东西都要转化为二进制才能被认识。要想让计算机能够处理字符,也要如此处理。必须把字符与二进制的位(bit)之间建立起一个对应关系,这种对应关系被称为“字符编码”。
1960年代,美国发布了“美国信息交换标准代码”(American Standard Code for Information Interchange,ASCII),主要规定了英语字符在计算机中的编码。1986年发布的最新版一共规定了128个字符的编码。
利用Python中的内置函数ord可以得到ASCII中某个字符编码的十进制表示。
这个十进制数字对应的二进制数字可以用bin()函数得到。
只不过在ASCII中,每个字符的编码只占用了1字节的后7位,最前面的一位统一规定为0,即为01000001。
ASCII对于英语而言已经足够了,但英语以外的语言则不然了。比如汉字,显然不能用1字节(1字节最多可以表示256种字符,而汉字多达10万个,最常用的大约有3000~4000个),于是就要用多字节表示一个字符。比如,简体中文编码GB2312使用2字节表示一个汉字,理论上能够支持256×256=65536个字符。
除了汉语,还有好多种语言,它们都需要让计算机认识,于是就有了各种样式的编码(仅中文就有多种编码,如GB2312编码、BIG5编码、HKSCS编码、GBK编码等)。结果导致同一个二进制数字在不同编码方案中对应着不同的字符,这就是“乱码”产生的原因之一。所以,需要一个统一的编码方案。
于是Unicode应运而生。Unicode的中文翻译为:万国码、国际码、统一码、单一码,目的是实现编码方案的“世界大同”,由位于美国加州的The Unicode Consortium(统一码联盟,一个非赢利机构)负责维护。
但是,Unicode 只是一个字符集,而没有制定编码规则,所以需要再制定Unicode的实现方式(Unicode Transformation Format,UTF),于是出现了UTF-16、UTF-32等。现在使用最广泛的是UTF-8(8-bit Unicode Transformation Format),也是互联网普遍采用的Unicode实现方式。图3-3-1所示的是本书作者所用的IDE设置的编码方式。
图3-3-1 IDE的编码设置
在交互模式中,还可以查看自己的计算机所使用的默认编码。下面的操作显示了作者所用计算机的编码方式。
在3.2.10节中写的程序文件中,第一行“#coding:utf-8”也是声明本程序文件所采用的字符编码方式是UTF-8(与所使用编辑器编码方式保持一致)。
在Python中,不但可以使用ord函数得到每个字符的编码,而且能够使用chr函数实现反向操作。
因为Python 3支持Unicode,所以每个汉字都对应一个编码数字。
解决了单个字符问题,就可以研究多个字符组成的“串”了。
3.3.2 认识字符串
其实,前面已经出现的“hello,world”就是一个字符串(string)。所谓字符串,在Python中就是用单引号或双引号包裹着的若干字符(见图3-3-2)。
图3-3-2 字符串组成部分
字符串也是Python中的内置对象类型,用type函数查看,返回值为str,它就是字符串的类型名称。
注意,包裹字符串的单引号或者双引号是字符串的标志,在键盘输入的时候必须是英文状态,并且要成对出现。
在引号中不仅可以放字母、汉字,还可以放其他字符,如数字。
尽管是数字,用引号包裹后,就不再是整数了,变成了字符串。这种由数字组成的字符串可以通过int函数和str函数实现相互间的类型转化。
请注意下面的写法:
出现了SyntaxError(语法错误)引导的提示,说明这里存在错误,错误的类型是SyntaxError,后面是对这种错误的解释:“invalid syntax”(无效的语法)。
在Python中,这一点是非常友好的,如果语句存在错误,就会将错误输出,供程序员参考改正。
print()是Python的一个内置函数,其作用是把参数中内容打印出来,通常显示在当前的交互模式所在的控制台。
解决上述错误的方法之一就是使用双引号和单引号的嵌套。
用双引号来包裹,双引号中允许出现单引号。其实,反过来,单引号中也可以包裹双引号。
此外,还有一种解决方法,那就是使用“转义符”。所谓转义,就是让某个符号不再表示原来的含义了。比如n,在字符串中就表示一个字母,这是原来的含义。
然而如果按照下面的方式操作之后,意思就变了。
注意观察这两句的区别,如图3-3-3所示。
图3-3-3 转义符的作用
第二句中的符号“\”将本来是字母n的含义转换,与“\”符号一同组成了“\n”,其含义是换行,因此就出现了第二句的操作结果。
同样,可以这样来做,实现“what's your name?”的正确显示,尽管依然使用单引号包裹字符串,但不会报错了。
表3-3-1中列出了Python中常用的转义字符及其说明,供应用时查阅。
表3-3-1 转义字符及其说明
建议读者在交互模式中测试上述转移符的显示效果。例如:
用“\”实现转义是一个方便的做法,但是如果在字符串中用到了“\”符号,怎么办?比如,打印Windows中的路径。
这个输出结果显然不是所需要的。如何解决?可以继续使用转义符:
此外,还有一种方法:
状如r"c:\news",由r开头引起的字符串就是“原始字符串”,在里面放任何字符都表示该字符的原始含义。这种方法在Web开发中设置网站目录结构的时候非常有用。
【例题3-3-1】 在交互模式中,用print函数打印苏轼的词《江城子·密州出猎》,要求每句占一行。
代码示例
>>> print("江城子·密州出猎\n\n老夫聊发少年狂,\n左牵黄,\n右擎苍,\n锦帽貂裘,\n千骑卷平冈。\n为报倾城随太守,\n亲射虎,\n看孙郎。\n\n酒酣胸胆尚开张,\n鬓微霜,\n又何妨?\n持节云中,\n何日遣冯唐?\n会挽雕弓如满月,\n西北望,\n射天狼。")
江城子·密州出猎
老夫聊发少年狂,
左牵黄,
右擎苍,
锦帽貂裘,
千骑卷平冈。
为报倾城随太守,
亲射虎,
看孙郎。
酒酣胸胆尚开张,
鬓微霜,
又何妨?
持节云中,
何日遣冯唐?
会挽雕弓如满月,
西北望,
射天狼。
3.3.3 字符串基本操作
以熟悉的“hello world”字符串为例,它包括英文字母和一个空格(字符),还有一个重要特征要引起注意:这些字母和字符是按照一定顺序排列的,不是随机排的,更不能随意更换顺序。
尽管a和b两个变量引用的字符串对象(当某个变量引用一个对象的时候,本书中会用那个变量来指代对象,如简称字符串a,事实上都是指变量a所引用的字符串对象)只有较小的差异,但a、b是两个不同的对象。
像字符串这样,其元素必须按照特定顺序排列的对象被称为“序列”。字符串是在本书中出现的第一种序列,在后续内容中,读者还能学习到其他序列对象。
字符串这样的序列存在着一系列共性的操作。
1.“+”:连接序列
对于数字,“+”的含义是实现两个数字相加,得到一个新的数字。对于字符串(序列),“+”的作用效果是将字符串连接起来,并得到了一个新的字符串。
注意,“+”连接的对象必须是同种类型的,否则报错。
如果非要实现字符串和数字的连接,应该如何操作?
可以使用类型转化的方式,将“+”两侧的对象转化为同种序列类型的对象。
2.“*”:重复元素
数值运算中的“*”表示的是乘法,对于字符串(序列),这个符号则表示要获得重复的元素。
3.len函数:求序列长度
len函数是一个内置函数,其作用是得到序列类对象的长度。
承接前面的操作,测量字符串r 的长度。
请读者认真数一数,会发现r中包含10个英文字母,不要漏掉两个单词中间的空格,它也算一个字符,所以有11个字符,即长度为11。
可以进一步看看函数len返回值的类型。
如果查看len函数的联机帮助文档,会发现这样的描述。
描述中说明len函数返回的是一个容器(container)的元素数。字符串的确像一个容器,里面按照一定顺序装入了若干字符。
4.in:判断元素是否存在其中
如前所述,字符串是一个容器,“in”用于判断容器中是否存在某个元素。
返回True,说明该字符(串)在容器中,否则返回False(关于True和False,详见4.1.3节的内容)。
【例题3-3-2】 连接“Life is short.”和“You need Python.”这两个字符串,并且用print函数在控制台上打印出来,显示为两行。
代码示例
3.3.4 索引和切片
作为序列中一员的字符串,每个字符都是按照特定顺序排列的,不能随意更换位置。因此,可以给每个字符进行编号。
由此,可以把“序列”理解为“有序排列”。在Python中,给这些编号取了一个文雅的名字,叫作“索引”(index。其他编程语言也这么称呼,不是Python独有的)。
给字符串中的字符(或序列中的元素)进行编号(即索引)的方法有两种:一种是从左边开始编号,依次为0、1、2、…,直到最右边的字符结束;另一种是从右边开始,依次是-1、-2、-3、…,直到最左边的字符结束。
如图3-3-4所示,对字符串中的所有字符建立索引。特别注意,两个单词中间的那个空格也占用了一个位置,空格是一个字符。“无”不完全等于“没有”。
图3-3-4 字符串中的索引
再有,因为可以从两个方向开始编号,所以每个字符可以有两个索引。
字符的索引建立好了,自然可以通过索引找到每个字符了。这就好比每个居民都有一个身份证号(理论上居民个人和身份证号是一对一的关系),通过身份证号(相当于索引)就能找到对应的人。在Python中,实现这种操作的方式是使用“[]”符号,如下操作:
变量r引用了字符串对象,后面的“[]”中是字符的索引。操作示例显示,不论是用从左边开始计数的索引还是用从右边开始计数的索引,都能得到那个字符。
如果使用的索引超出了该字符串的索引范围,则会报错。
为了避免这种情况,需要提前知道最后一个字符的索引才好。一种方式是通过len函数得到字符串的字符数(长度),即可知道最大索引了。另一种方式是使用下面的方式直接得到最右边的字符的索引。
index()是字符串对象的一个方法(关于对象的“方法”,请参考3.1节的简述),能够得到某字符在字符串中的索引(按照从左开始计数的索引),且是第一次出现。
通过索引,不仅可以得到某个指定字符,还能得到若干字符。操作方式如下:
通过r[1:8]方式从字符串r 中“得到”了多个(不是一个)字符——称为“切片”(slice)。如图3-3-5所示,将索引是1、2、3、4、5、6、7的字符“切”出来。从结果中可以看出,结束索引8所对应的字符没有被“切”下来,这是Python中的普遍规则,简单概括为:前包括,后不包括。
图3-3-5 字符串切片
原字符串被“切”出了一部分,但并没有因此而破坏原字符串。
这说明“切片”是比照着索引在原字符串中所对应的字符,重新创建了一个字符串对象。从最终结果看,貌似是从原字符串上“切下来的一部分”。
如果用一个普遍的表达式来说明切片操作的方式,可以表示为:
❖ indexstart:表示开始的索引。如果是从第一个字符开始,可以省略。
❖ indexstop:表示结束的索引(切片中不含此索引对应的字符)。如果是到最后一个字符结束(含最后一个),可以省略。
❖ step:表示步长,默认为1;可为正整数,也可为负整数。
省略indexstart索引,表示切片以字符串的开始为开始。对照图3-3-5可知,索引8和-3对应的是同一个字符(作为结束字符,不包含在切片中),所以上述两种操作结果一样。
同理,如果省略indexstop,则表示到字符串结束,即切片中的字符直到原字符串最后一个字符为止,并包含最后一个字符。
以上操作中,步长都省略,即:使用了步长的默认值1。
r[1:]和r[1::1]的操作结果是一样的。但是,如果设置步长不是1,则会按照步长切片。
请读者通过操作比较r[:8]和r[0:8],以及r[1:]和r[1:10]的结果,结合前面的内容给予理解。
在r[1::1]和r[::2]操作中,步长分别为1和2,都是正整数。前面介绍步长的时候特别提到,它也可以为负整数。
r[::-1]中的步长为负数,结果得到了原字符串的反转。
这个反转是如何得到的呢?先要理解步长(step)“正负”含义。当步长为正整数时,相当于“站在”了字符串的左侧“看”字符串中的每个字符(见图3-3-6)。先看到的字符所对应的索引就是indexstart,后看到的字符所对应的索引就是indexstop。
图3-3-6 步长为正整数
注意,索引不区分是从左边开始计数,还是从右边开始计数,如下述操作的结果都一样。
当切片步长为负整数的时候,与上述不同的是调整了“看”列表的位置(见图3-3-7),改为“站在右侧看”,其他原则不变,即:依然是先看到的字符对应的索引是indexstart,后看到的字符对应的索引是indexstop。
图3-3-7 步长为负整数
以下各项操作同样等效。
理解了上述切片原则后,再看r[::-1]就不难理解了。步长为-1,即为“站在右侧看”,从“看到”的第一个字符开始(r[-1]对应的字符),到最后即最左侧的字符(r[0]对应的字符),并包括最后一个为止。因此得到了一个相对于原来的反转的字符串。
此处以字符串为例介绍了切片的基本方法,这种方法适用于所有序列类型的对象。
【例题3-3-3】 对字符串“123456789”,通过切片操作得到如下结果:
❖ 得到“2468”。
❖ 得到“1234”。
❖ 得到“963”。
❖ 得到“69”。
❖ 得到“987654321”。
代码示例
3.3.5 键盘输入
计算机(“电脑”可能更形象)的智能,一种体现就是可以接收用户通过键盘输入的内容。Python提供了内置函数input,用于接收用户通过键盘输入的信息。
从联机帮助文档中已经清晰地看到了input 函数的使用方法,下面在交互模式中操练此函数。
如上述操作,通过键盘输入“python”后,回车,将所输入的内容作为返回值呈现。
从帮助文档中可知,input 函数的返回值是一个字符串类型的对象,于是使用下述方式,用变量引用此返回值对象。
不论通过键盘输入什么字符,input函数的返回值都是字符串。
有了以上两个准备,接下来就可以写一个能够“对话”的小程序了。
【例题3-3-4】 编写一段程序,实现如下功能:
(1)询问姓名和年龄。
(2)计算10年之后的年龄。
(3)打印出所输入的姓名和当前年龄、10年之后的年龄。
代码示例
在本程序中需要注意的是类型转化。①中的变量age引用的是一个字符串类型的对象,这个对象必须转化为整数类型之后才能参与②中的运算,所以在②中使用了int(age)。而当一个整数与字符串通过“+”连接的时候,又需要转化为字符串类型,③中的str函数即此意图。
请读者自行调试这个程序,如果遇到了报错,请认真看报错信息,从中找到修改的方向。
上述代码示例并非十分完美,只是局限于本书已经讲述过的知识实现了一些基本功能。关于字符串的更多内容,还有待于深入学习它的更多方法。
3.3.6 字符串的方法
字符串是对象类型,也是对象,因此它会有一些方法供开发者使用,而且这些方法已经内置好了——内置对象,必须如此。
通过dir(str)看到了字符串对象的所有属性和方法,可以粗略地划分为两类:一类是名称以双下滑线开始和结尾的,称为“特殊方法”和“特殊属性”;另一类是用看起来很普通的名称,如capitalize等,笼统称为“方法”和“属性”。
对于内置对象的方法而言,因为调用的时候类似使用函数,所以也有资料称之为某对象的函数。本书按照对象的方法来称呼。在后续学习中,我们会区分函数和方法的含义。
字符串有那么多方法,这里不会一一介绍,仅选几个,用以示例如何通过联机帮助文档学习其使用方法。
在3.3.4节提到过一个名为index()的方法,使用它可以得到字符串中某个字符的索引。
以字符串中的index()方法为例,解读帮助文档的含义,理解如何使用它。如图3-3-8所示,根据图中所标识的各项建议,在交互模式中进行适当练习,从而理解各项含义。
图3-3-8 分解index方法
没有规定查找的索引范围,即没有给start和end参数传值,则默认为在整个字符串的范围查找,并且返回找到的第一个子字符串的索引。
指定了开始查找的索引,即start=20,于是从这个位置开始向后查找,并且返回在这个范围内所找到的第一个子字符串的索引。
更准确的理解还依赖于读者认真阅读帮助文档中的说明。
后续的内容中,本书不再呈现帮助文档,但不意味着这种方法不重要,而是相当重要,只是受到篇幅的限制,并且查看文档的方法也不难。
1.“is”开头的方法
仔细观察dir(str)的结果,其中有若干以“is”作为名称开始的方法。这些方法无一例外,都是返回了bool类型。这种类型对象会在4.1.3节中详述,中文为“布尔”类型,只有True和False两个值。例如:
字符串的isdigit()方法是用来判断当前字符串对象是否完全由数字字符(即键盘上的1、2、3、4、5、6、7、8、9、0)组成。如果是,则返回True,否则返回False。
其他若干类似的方法,基本操作和结构都与上述示例类似,请读者逐一查看联机文档的说明,并在交互模式中进行调试。
2.分隔和组合
字符串对象提供了根据某个符号分割字符串内容的方法split()。
这是用空格作为分割符,得到了一个列表(List)类型的返回值(详见3.4节)。
帮助文档中对分隔符做了说明,请认真阅读。特别注意,如果没有指定特定的分隔符,Python会默认空格为分隔符。
注意比较上面三种不同的情况。
除了可以依据某个字符对字符串进行划分,还可以用某个字符把另一种对象组合成为一个字符串,这个过程有点类似split()方法的逆向过程,使用的是字符串的join()方法。
如果读者查看联机帮助文档,会看到“S.join(iterable) -> str”(特别建议认真阅读帮助文档的内容),这个方法的参数中出现了“iterable”,其含义为“可迭代的”。这是某些对象所具有的特征,包括字符串在内的一些对象,被称为可迭代对象(关于“可迭代的”和“可迭代对象”详见6.10节)。通过字符串的split()方法得到的名为列表的对象,就是具有“可迭代的”特点的对象,因此下面演示就使用这个对象。
③中使用了字符串"-"的方法join(),参数是前面得到的列表lst,意图是要用“-”符号把列表lst中的元素连接起来,并且返回一个大字符串。
刚才提到字符串也是“可迭代的”,所以join()方法的参数也可以用字符串。只不过字符串的元素是字符,所以,如果用空格——也是字符——的join()方法的话,得到的结果就是④所返回的那样,每个字符之间有了一个空格。
字符串的方法众多,本书仅选几个作为示例,在后续内容中也会不断用到其他方法。读者要通过本例掌握查看文档的方法。
【例题3-3-5】 有的字符串两边有空格,或者一边有空格,用字符串中的方法,将这些空格去掉。
代码示例
3.3.7 字符串格式化输出
在字符串的诸多方法中有一个名为format的方法,下面就用它来实现“格式化输出”。
要了解这个方法的使用,还是要查看其文档,请读者自行完成。下面列举几种使用format()方法进行格式化输出的方式。
在交互模式中,输入了字符串"I like{0}and{1}",其中用{0}和{1}占据了两个位置,它们就是占位符。format("python","physics")是字符串格式化输出的方法,传入了两个字符串。第一个字符串“python”对应占位符{0};第二个字符串“physics”对应占位符{1},即占位符中的数字,就是参数format()方法的参数列表的顺序号(见图3-3-9)。
图3-3-9 format()方法使用解析
为了进一步理解占位符中的数字的含义,可以做如下操作。
既然format()实现的是“格式化”输出,就应该可以指定某种“格式”,让输出的结果符合指定的样式。
{0:10}表示该位置有10个字符,并且放在这个位置的字符是左对齐;{1:>15}表示该位置有15个字符,并且放在这个位置的字符是右对齐;{0:^10}和{1:^15}则表示字符串在该位置的对齐方式是居中。
除了规定字符串的对齐方式,还可以限制显示的字符个数。
{0:.2}中的“.2”表示对于传入的字符串,截取前两个字符。需要注意,在“:”后面和“.”前面没有任何数字,意思是该位置的长度自动适应即将放到该位置的字符串。
{1:^15.3}中的“15.3”表示该位置的长度是15个字符,但即将放入该位置的字符串应该仅有3个字符,也就是要从传入的字符串"physics"中截取前3个字符,即"phy"。
format()中,除了能够传入字符串,还可以传入数字(包括整数和浮点数),而且有各种花样。
{0:d}表示在该位置放第一个参数,且为整数;{1:f}表示该位置放第二个参数,且为浮点数,此处浮点数的小数位数是默认的。
用{0:4d}设置此位置的长度是4个字符,并且在其中应该放置整数,在默认状态下,整数是右对齐;{1:.1f}表示该位置的浮点数小数位数为1位,并且自动采用四舍五入方式对参数中小数进行位数截取操作,默认也是右对齐。
其中,{0:04d}和{1:06.1f}表示在该位置的空位用0填充。
使用字符串的format()方法进行格式化输出,实现的方式多种多样,除了上述演示,还可以这样做:
【例题3-3-6】 字符串有format方法,内置函数中也有format方法。用示例说明内置函数format()的使用方法。
代码示例