Python大学实用教程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.3 字符和字符串

观察计算机的键盘,上面每个键对应着一个“字符”(character)。注意,字符不仅是字母,通常包括字母、数字、标点符号和一些控制符等。当计算机能够处理字符后,它就不再单纯“计算”了,而是具有了“电脑”的功能。

3.3.1 字符编码

如前所述,计算机只能认识二进制数字,所有提交给它的东西都要转化为二进制才能被认识。要想让计算机能够处理字符,也要如此处理。必须把字符与二进制的位(bit)之间建立起一个对应关系,这种对应关系被称为“字符编码”。

1960年代,美国发布了“美国信息交换标准代码”(American Standard Code for Information Interchange,ASCII),主要规定了英语字符在计算机中的编码。1986年发布的最新版一共规定了128个字符的编码。

利用Python中的内置函数ord可以得到ASCII中某个字符编码的十进制表示。

img

这个十进制数字对应的二进制数字可以用bin()函数得到。

img

只不过在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设置的编码方式。

img

图3-3-1 IDE的编码设置

在交互模式中,还可以查看自己的计算机所使用的默认编码。下面的操作显示了作者所用计算机的编码方式。

img
img

在3.2.10节中写的程序文件中,第一行“#coding:utf-8”也是声明本程序文件所采用的字符编码方式是UTF-8(与所使用编辑器编码方式保持一致)。

在Python中,不但可以使用ord函数得到每个字符的编码,而且能够使用chr函数实现反向操作。

img

因为Python 3支持Unicode,所以每个汉字都对应一个编码数字。

img

解决了单个字符问题,就可以研究多个字符组成的“串”了。

3.3.2 认识字符串

其实,前面已经出现的“hello,world”就是一个字符串(string)。所谓字符串,在Python中就是用单引号或双引号包裹着的若干字符(见图3-3-2)。

img

图3-3-2 字符串组成部分

img

字符串也是Python中的内置对象类型,用type函数查看,返回值为str,它就是字符串的类型名称。

注意,包裹字符串的单引号或者双引号是字符串的标志,在键盘输入的时候必须是英文状态,并且要成对出现。

img

在引号中不仅可以放字母、汉字,还可以放其他字符,如数字。

img
img

尽管是数字,用引号包裹后,就不再是整数了,变成了字符串。这种由数字组成的字符串可以通过int函数和str函数实现相互间的类型转化。

img

请注意下面的写法:

img

出现了SyntaxError(语法错误)引导的提示,说明这里存在错误,错误的类型是SyntaxError,后面是对这种错误的解释:“invalid syntax”(无效的语法)。

在Python中,这一点是非常友好的,如果语句存在错误,就会将错误输出,供程序员参考改正。

print()是Python的一个内置函数,其作用是把参数中内容打印出来,通常显示在当前的交互模式所在的控制台。

解决上述错误的方法之一就是使用双引号和单引号的嵌套。

img

用双引号来包裹,双引号中允许出现单引号。其实,反过来,单引号中也可以包裹双引号。

此外,还有一种解决方法,那就是使用“转义符”。所谓转义,就是让某个符号不再表示原来的含义了。比如n,在字符串中就表示一个字母,这是原来的含义。

img

然而如果按照下面的方式操作之后,意思就变了。

img

注意观察这两句的区别,如图3-3-3所示。

img

图3-3-3 转义符的作用

第二句中的符号“\”将本来是字母n的含义转换,与“\”符号一同组成了“\n”,其含义是换行,因此就出现了第二句的操作结果。

同样,可以这样来做,实现“what's your name?”的正确显示,尽管依然使用单引号包裹字符串,但不会报错了。

img

表3-3-1中列出了Python中常用的转义字符及其说明,供应用时查阅。

表3-3-1 转义字符及其说明

img

建议读者在交互模式中测试上述转移符的显示效果。例如:

img

用“\”实现转义是一个方便的做法,但是如果在字符串中用到了“\”符号,怎么办?比如,打印Windows中的路径。

img

这个输出结果显然不是所需要的。如何解决?可以继续使用转义符:

img

此外,还有一种方法:

img

状如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”字符串为例,它包括英文字母和一个空格(字符),还有一个重要特征要引起注意:这些字母和字符是按照一定顺序排列的,不是随机排的,更不能随意更换顺序。

img

尽管a和b两个变量引用的字符串对象(当某个变量引用一个对象的时候,本书中会用那个变量来指代对象,如简称字符串a,事实上都是指变量a所引用的字符串对象)只有较小的差异,但a、b是两个不同的对象。

像字符串这样,其元素必须按照特定顺序排列的对象被称为“序列”。字符串是在本书中出现的第一种序列,在后续内容中,读者还能学习到其他序列对象。

字符串这样的序列存在着一系列共性的操作。

1.“+”:连接序列

对于数字,“+”的含义是实现两个数字相加,得到一个新的数字。对于字符串(序列),“+”的作用效果是将字符串连接起来,并得到了一个新的字符串。

img

注意,“+”连接的对象必须是同种类型的,否则报错。

img
img

如果非要实现字符串和数字的连接,应该如何操作?

img

可以使用类型转化的方式,将“+”两侧的对象转化为同种序列类型的对象。

2.“*”:重复元素

数值运算中的“*”表示的是乘法,对于字符串(序列),这个符号则表示要获得重复的元素。

img

3.len函数:求序列长度

len函数是一个内置函数,其作用是得到序列类对象的长度。

承接前面的操作,测量字符串r 的长度。

img

请读者认真数一数,会发现r中包含10个英文字母,不要漏掉两个单词中间的空格,它也算一个字符,所以有11个字符,即长度为11。

可以进一步看看函数len返回值的类型。

img

如果查看len函数的联机帮助文档,会发现这样的描述。

img

描述中说明len函数返回的是一个容器(container)的元素数。字符串的确像一个容器,里面按照一定顺序装入了若干字符。

4.in:判断元素是否存在其中

如前所述,字符串是一个容器,“in”用于判断容器中是否存在某个元素。

img

返回True,说明该字符(串)在容器中,否则返回False(关于True和False,详见4.1.3节的内容)。

【例题3-3-2】 连接“Life is short.”和“You need Python.”这两个字符串,并且用print函数在控制台上打印出来,显示为两行。

代码示例

img

3.3.4 索引和切片

作为序列中一员的字符串,每个字符都是按照特定顺序排列的,不能随意更换位置。因此,可以给每个字符进行编号。

由此,可以把“序列”理解为“有序排列”。在Python中,给这些编号取了一个文雅的名字,叫作“索引”(index。其他编程语言也这么称呼,不是Python独有的)。

给字符串中的字符(或序列中的元素)进行编号(即索引)的方法有两种:一种是从左边开始编号,依次为0、1、2、…,直到最右边的字符结束;另一种是从右边开始,依次是-1、-2、-3、…,直到最左边的字符结束。

如图3-3-4所示,对字符串中的所有字符建立索引。特别注意,两个单词中间的那个空格也占用了一个位置,空格是一个字符。“无”不完全等于“没有”。

img

图3-3-4 字符串中的索引

再有,因为可以从两个方向开始编号,所以每个字符可以有两个索引。

字符的索引建立好了,自然可以通过索引找到每个字符了。这就好比每个居民都有一个身份证号(理论上居民个人和身份证号是一对一的关系),通过身份证号(相当于索引)就能找到对应的人。在Python中,实现这种操作的方式是使用“[]”符号,如下操作:

img

变量r引用了字符串对象,后面的“[]”中是字符的索引。操作示例显示,不论是用从左边开始计数的索引还是用从右边开始计数的索引,都能得到那个字符。

如果使用的索引超出了该字符串的索引范围,则会报错。

img
img

为了避免这种情况,需要提前知道最后一个字符的索引才好。一种方式是通过len函数得到字符串的字符数(长度),即可知道最大索引了。另一种方式是使用下面的方式直接得到最右边的字符的索引。

img

index()是字符串对象的一个方法(关于对象的“方法”,请参考3.1节的简述),能够得到某字符在字符串中的索引(按照从左开始计数的索引),且是第一次出现。

img

通过索引,不仅可以得到某个指定字符,还能得到若干字符。操作方式如下:

img

通过r[1:8]方式从字符串r 中“得到”了多个(不是一个)字符——称为“切片”(slice)。如图3-3-5所示,将索引是1、2、3、4、5、6、7的字符“切”出来。从结果中可以看出,结束索引8所对应的字符没有被“切”下来,这是Python中的普遍规则,简单概括为:前包括,后不包括。

img

图3-3-5 字符串切片

原字符串被“切”出了一部分,但并没有因此而破坏原字符串。

img

这说明“切片”是比照着索引在原字符串中所对应的字符,重新创建了一个字符串对象。从最终结果看,貌似是从原字符串上“切下来的一部分”。

如果用一个普遍的表达式来说明切片操作的方式,可以表示为:

img

❖ indexstart:表示开始的索引。如果是从第一个字符开始,可以省略。

❖ indexstop:表示结束的索引(切片中不含此索引对应的字符)。如果是到最后一个字符结束(含最后一个),可以省略。

❖ step:表示步长,默认为1;可为正整数,也可为负整数。

img
img

省略indexstart索引,表示切片以字符串的开始为开始。对照图3-3-5可知,索引8和-3对应的是同一个字符(作为结束字符,不包含在切片中),所以上述两种操作结果一样。

同理,如果省略indexstop,则表示到字符串结束,即切片中的字符直到原字符串最后一个字符为止,并包含最后一个字符。

img

以上操作中,步长都省略,即:使用了步长的默认值1。

img

r[1:]和r[1::1]的操作结果是一样的。但是,如果设置步长不是1,则会按照步长切片。

img

请读者通过操作比较r[:8]和r[0:8],以及r[1:]和r[1:10]的结果,结合前面的内容给予理解。

在r[1::1]和r[::2]操作中,步长分别为1和2,都是正整数。前面介绍步长的时候特别提到,它也可以为负整数。

img

r[::-1]中的步长为负数,结果得到了原字符串的反转。

这个反转是如何得到的呢?先要理解步长(step)“正负”含义。当步长为正整数时,相当于“站在”了字符串的左侧“看”字符串中的每个字符(见图3-3-6)。先看到的字符所对应的索引就是indexstart,后看到的字符所对应的索引就是indexstop

img

图3-3-6 步长为正整数

注意,索引不区分是从左边开始计数,还是从右边开始计数,如下述操作的结果都一样。

img

当切片步长为负整数的时候,与上述不同的是调整了“看”列表的位置(见图3-3-7),改为“站在右侧看”,其他原则不变,即:依然是先看到的字符对应的索引是indexstart,后看到的字符对应的索引是indexstop

img

图3-3-7 步长为负整数

以下各项操作同样等效。

img

理解了上述切片原则后,再看r[::-1]就不难理解了。步长为-1,即为“站在右侧看”,从“看到”的第一个字符开始(r[-1]对应的字符),到最后即最左侧的字符(r[0]对应的字符),并包括最后一个为止。因此得到了一个相对于原来的反转的字符串。

此处以字符串为例介绍了切片的基本方法,这种方法适用于所有序列类型的对象。

【例题3-3-3】 对字符串“123456789”,通过切片操作得到如下结果:

❖ 得到“2468”。

❖ 得到“1234”。

❖ 得到“963”。

❖ 得到“69”。

❖ 得到“987654321”。

代码示例

img

3.3.5 键盘输入

计算机(“电脑”可能更形象)的智能,一种体现就是可以接收用户通过键盘输入的内容。Python提供了内置函数input,用于接收用户通过键盘输入的信息。

img

从联机帮助文档中已经清晰地看到了input 函数的使用方法,下面在交互模式中操练此函数。

img

如上述操作,通过键盘输入“python”后,回车,将所输入的内容作为返回值呈现。

从帮助文档中可知,input 函数的返回值是一个字符串类型的对象,于是使用下述方式,用变量引用此返回值对象。

img

不论通过键盘输入什么字符,input函数的返回值都是字符串。

img

有了以上两个准备,接下来就可以写一个能够“对话”的小程序了。

【例题3-3-4】 编写一段程序,实现如下功能:

(1)询问姓名和年龄。

(2)计算10年之后的年龄。

(3)打印出所输入的姓名和当前年龄、10年之后的年龄。

代码示例

img
img

在本程序中需要注意的是类型转化。①中的变量age引用的是一个字符串类型的对象,这个对象必须转化为整数类型之后才能参与②中的运算,所以在②中使用了int(age)。而当一个整数与字符串通过“+”连接的时候,又需要转化为字符串类型,③中的str函数即此意图。

请读者自行调试这个程序,如果遇到了报错,请认真看报错信息,从中找到修改的方向。

上述代码示例并非十分完美,只是局限于本书已经讲述过的知识实现了一些基本功能。关于字符串的更多内容,还有待于深入学习它的更多方法。

3.3.6 字符串的方法

字符串是对象类型,也是对象,因此它会有一些方法供开发者使用,而且这些方法已经内置好了——内置对象,必须如此。

img

通过dir(str)看到了字符串对象的所有属性和方法,可以粗略地划分为两类:一类是名称以双下滑线开始和结尾的,称为“特殊方法”和“特殊属性”;另一类是用看起来很普通的名称,如capitalize等,笼统称为“方法”和“属性”。

对于内置对象的方法而言,因为调用的时候类似使用函数,所以也有资料称之为某对象的函数。本书按照对象的方法来称呼。在后续学习中,我们会区分函数和方法的含义。

字符串有那么多方法,这里不会一一介绍,仅选几个,用以示例如何通过联机帮助文档学习其使用方法。

在3.3.4节提到过一个名为index()的方法,使用它可以得到字符串中某个字符的索引。

img
img

以字符串中的index()方法为例,解读帮助文档的含义,理解如何使用它。如图3-3-8所示,根据图中所标识的各项建议,在交互模式中进行适当练习,从而理解各项含义。

img
img

图3-3-8 分解index方法

没有规定查找的索引范围,即没有给start和end参数传值,则默认为在整个字符串的范围查找,并且返回找到的第一个子字符串的索引。

img

指定了开始查找的索引,即start=20,于是从这个位置开始向后查找,并且返回在这个范围内所找到的第一个子字符串的索引。

更准确的理解还依赖于读者认真阅读帮助文档中的说明。

后续的内容中,本书不再呈现帮助文档,但不意味着这种方法不重要,而是相当重要,只是受到篇幅的限制,并且查看文档的方法也不难。

1.“is”开头的方法

仔细观察dir(str)的结果,其中有若干以“is”作为名称开始的方法。这些方法无一例外,都是返回了bool类型。这种类型对象会在4.1.3节中详述,中文为“布尔”类型,只有True和False两个值。例如:

img

字符串的isdigit()方法是用来判断当前字符串对象是否完全由数字字符(即键盘上的1、2、3、4、5、6、7、8、9、0)组成。如果是,则返回True,否则返回False。

其他若干类似的方法,基本操作和结构都与上述示例类似,请读者逐一查看联机文档的说明,并在交互模式中进行调试。

2.分隔和组合

字符串对象提供了根据某个符号分割字符串内容的方法split()。

img

这是用空格作为分割符,得到了一个列表(List)类型的返回值(详见3.4节)。

帮助文档中对分隔符做了说明,请认真阅读。特别注意,如果没有指定特定的分隔符,Python会默认空格为分隔符。

img

注意比较上面三种不同的情况。

除了可以依据某个字符对字符串进行划分,还可以用某个字符把另一种对象组合成为一个字符串,这个过程有点类似split()方法的逆向过程,使用的是字符串的join()方法。

如果读者查看联机帮助文档,会看到“S.join(iterable) -> str”(特别建议认真阅读帮助文档的内容),这个方法的参数中出现了“iterable”,其含义为“可迭代的”。这是某些对象所具有的特征,包括字符串在内的一些对象,被称为可迭代对象(关于“可迭代的”和“可迭代对象”详见6.10节)。通过字符串的split()方法得到的名为列表的对象,就是具有“可迭代的”特点的对象,因此下面演示就使用这个对象。

img

③中使用了字符串"-"的方法join(),参数是前面得到的列表lst,意图是要用“-”符号把列表lst中的元素连接起来,并且返回一个大字符串。

img

刚才提到字符串也是“可迭代的”,所以join()方法的参数也可以用字符串。只不过字符串的元素是字符,所以,如果用空格——也是字符——的join()方法的话,得到的结果就是④所返回的那样,每个字符之间有了一个空格。

字符串的方法众多,本书仅选几个作为示例,在后续内容中也会不断用到其他方法。读者要通过本例掌握查看文档的方法。

【例题3-3-5】 有的字符串两边有空格,或者一边有空格,用字符串中的方法,将这些空格去掉。

代码示例

img

3.3.7 字符串格式化输出

在字符串的诸多方法中有一个名为format的方法,下面就用它来实现“格式化输出”。

要了解这个方法的使用,还是要查看其文档,请读者自行完成。下面列举几种使用format()方法进行格式化输出的方式。

img

在交互模式中,输入了字符串"I like{0}and{1}",其中用{0}和{1}占据了两个位置,它们就是占位符。format("python","physics")是字符串格式化输出的方法,传入了两个字符串。第一个字符串“python”对应占位符{0};第二个字符串“physics”对应占位符{1},即占位符中的数字,就是参数format()方法的参数列表的顺序号(见图3-3-9)。

img

图3-3-9 format()方法使用解析

为了进一步理解占位符中的数字的含义,可以做如下操作。

img

既然format()实现的是“格式化”输出,就应该可以指定某种“格式”,让输出的结果符合指定的样式。

img

{0:10}表示该位置有10个字符,并且放在这个位置的字符是左对齐;{1:>15}表示该位置有15个字符,并且放在这个位置的字符是右对齐;{0:^10}和{1:^15}则表示字符串在该位置的对齐方式是居中。

除了规定字符串的对齐方式,还可以限制显示的字符个数。

img

{0:.2}中的“.2”表示对于传入的字符串,截取前两个字符。需要注意,在“:”后面和“.”前面没有任何数字,意思是该位置的长度自动适应即将放到该位置的字符串。

{1:^15.3}中的“15.3”表示该位置的长度是15个字符,但即将放入该位置的字符串应该仅有3个字符,也就是要从传入的字符串"physics"中截取前3个字符,即"phy"。

format()中,除了能够传入字符串,还可以传入数字(包括整数和浮点数),而且有各种花样。

img

{0:d}表示在该位置放第一个参数,且为整数;{1:f}表示该位置放第二个参数,且为浮点数,此处浮点数的小数位数是默认的。

img

用{0:4d}设置此位置的长度是4个字符,并且在其中应该放置整数,在默认状态下,整数是右对齐;{1:.1f}表示该位置的浮点数小数位数为1位,并且自动采用四舍五入方式对参数中小数进行位数截取操作,默认也是右对齐。

img

其中,{0:04d}和{1:06.1f}表示在该位置的空位用0填充。

使用字符串的format()方法进行格式化输出,实现的方式多种多样,除了上述演示,还可以这样做:

img

【例题3-3-6】 字符串有format方法,内置函数中也有format方法。用示例说明内置函数format()的使用方法。

代码示例

img