让Python遇上Office:从编程入门到自动化办公实践
上QQ阅读APP看书,第一时间看更新

2.2 字符串

字符串是我们最常用的数据类型,也是六大类型中最重要、知识点最多的数据类型,所以,如果此刻的你正在犯困,请伸个懒腰,然后跟着我的步骤拿下字符串。顾名思义,所谓字符串就是一串字符,我想你应该知道什么是字符,如果不知道的话,我们有必要先来认识一下字符。

2.2.1 字符编码

前面我们说了,计算机只能识别通电和断电两种信号,这种特点被设计成由0和1组成的二进制数据,而二进制又可以轻易转换成十进制或其他进制,即扩展到整数。如果我们想表示字符,可以通过十进制数映射,比如说十进制65表示“A”,十进制66表示“B”,这种映射我们把它叫作编码,比较典型的一种编码叫作ASCII编码,如表 2-1所示。

表2-1 ASCII对照表

续表

在计算机中1个二进制数称为1位或1比特(Bit),8比特表示1字节(Byte),即8个二进制数表示1字节,表示为8Bit=1Byte,ASCII编码是7位或8位表示一个字符,前者叫标准ASCII,后者叫拓展ASCII,所以拓展ASCII编码的每个字符占用一个字节。字符“A”对应的十进制是65,二进制是01000001,可以看出如果转换成二进制的数据后位数不够8位会在左边补0。而8位二进制数的最大值是11111111,即十进制的255,换一句话说,ASCII最多能表示256(0~255)个字符,能满足英文编码,因为英文只有26个字母再加上其他一些符号,256个字符基本够用了,但其他语言,比如说我们的汉字就有九万多个,更别说世界上还有那么多个国家和地区的语言符号,ASCII明显不能满足编码需求。于是,很多国家都开始有了自己的编码,比如说中国用gbk编码表示中文字符,这样造成的结果就是编码混乱,所以你有时候打开一份文件看到的是乱码,很可能是没有指定正确的解码方式。

为了统一编码,后来出现了一种叫作Unicode的编码,意在用一种编码表示全世界的字符,虽然可行,但这种编码统一用2字节进行编码,本来1个英文字符只需要1字节就能表示,非要弄成2个字节,占用的资源就多了一倍,明显不太友好。后来又经过改善,出现了一种叫作utf-8的编码,这种编码可以动态改变字节数,比如说英文字符占用1字节,常用汉字占用3字节,偏僻汉字占用4~6字节,这样既能表示全世界的字符,又尽可能节省资源。正是由于utf-8这种可以节省资源的特性,现在该编码已经得到广泛应用,后面我们在进行文件操作的时候也经常使用这种编码。

说了那么多,大家应该已经了解什么是字符了,简单来说,字符就是经过某种编码得到的符号,比如字母、汉字、标点符号等都属于字符。我们记不住那么多字符,如果你想查看某个字符或编码的ASCII映射,我们可以通过Python提供的ord()和chr()函数,代码如下:

print(ord("A"))  # 输出:65
print(ord("a"))  # 输出:97
print(chr(65))  # 输出:A

从上面的代码可以看出字符“A”和字符“a”的ASCII是不一样的,这也是为什么Python要严格区分大小写的原因。

2.2.2 字符串

字符是单个的,如果把多个字符连在一起就成了字符串,所以本质上字符串就是字符。在Python中,字符串的类型是str,写法是用半角引号把字符引起来,可以是单引号、双引号或者三引号,只要用了引号就是字符串。注意两点:引号必须是半角的,也必须成对出现。代码如下:

a = 'hello'
print(type(a), a)  # 输出:<class 'str'> hello
b = "hello"
print(type(b), b)  # <class 'str'> hello
c = '''Python'''
print(type(c), c)  # 输出:<class 'str'> Python
d = """Python"""
print(type(d), d)  # 输出:<class 'str'> Python

上面的代码中变量a、b、c、d都是字符串,a和b完全等价,c和d完全等价。在用法上,单引号和双引号没有任何区别,但三引号就不一样了,三引号可以保留换行而单引号和双引号不行,如果你需要在单引号或双引号里换行,要用到“\n”这个换行符,下面的两种写法是完全等价的。代码如下:

print("\n谁言别后终无悔,\n寒月清宵绮梦回。\n深知身在情长在,\n前尘不共彩
云飞。\n")
print("""
谁言别后终无悔,
寒月清宵绮梦回。
深知身在情长在,
前尘不共彩云飞。
""")

正是由于三引号里的字符串可以随意换行,所以很多人都喜欢把注释内容写在三引号里,因为如果使用“#”写注释,那么每行文本前面都要加上一个“#”,就显得有点麻烦了。

那么多种引号都能表示字符串,而引号又必须成对出现,所以我们要灵活使用引号。如果一个字符串里面需要用到引号,有两种解决办法:一种是改变最外层的引号,比如说字符串内部包含了单引号或三引号,可以使用双引号表示字符;另一种是在引号前面加一个反斜杠进行转义,代码如下:

print("It's very cool!")  # 输出:It's very cool!
print('It\'s very cool!')  # 输出:It's very cool!

第一种方法很容易理解,用最外面的一对双引号表示字符串,字符串里面就算有成对单引号或三引号,只要没有其他双引号,这个字符串就不会有问题。第二种写法是用最外面的一对单引号表示字符串,虽然内部也出现了单引号,但给这个单引号加了一个反斜杠,表示对其转义,即告诉Python,内部的单引号只是一个普通的字符,不要与最左边的单引号进行配对。

2.2.3 转义字符

我们之前已经用过转义字符了,你应该还记得“\n”,“n”本来是一个普通字符,加上反斜杠之后就表示换行了,类似的还有“\t”表示制表符(即按一下键盘上的Tab键)。问题来了,有时候我就想打印出“\n”这个字符而不是让它换行那该怎么办呢?很简单,你在反斜杠前面再加一个反斜杠告诉Python这个反斜杠是普通字符,不表示转义,我这么描述你可能会有点懵,来看一下下面的代码就懂了:

print("我们知道"\n"表示换行")  # 输出:我们知道"
                                                 "表示换行
print("我们知道"\\n"表示换行")  # 输出:我们知道"\n"表示换行

以后我们学到文件操作的时候经常需要写文件路径,不巧的是Windows系统的文件路径是使用反斜杠分隔的,如果你比较有耐心,在每一个反斜杠前面都手动加上一个反斜杠也是可以的。但为了效率,我还是建议使用在字符串前面加上一个“r”的写法,即告诉Python字符串里的所有反斜杠都是普通字符而不是转义字符。看一下例子,下面两种写法完全等价:

print("C:\\Users\\admin\\Documents\\WeChat Files\\Applet")
# 输出:C:\Users\admin\Documents\WeChat Files\Applet
print(r"C:\Users\admin\Documents\WeChat Files\Applet")
# 输出:C:\Users\admin\Documents\WeChat Files\Applet

2.2.4 字符串索引

一个字符串可以包含0个或多个字符,每一个字符也称为元素。如果你拿到一个数据是字符串类型,但只需要其中的某个字符,我们可以通过元素位置去访问字符。千万注意,与我们的生活习惯不同,编程语言的位置基本上都是从0开始的,即第0个元素、第1个元素、第2个元素......。如果要访问字符串某个位置的元素,可以使用半角的中括号,如下面的代码所示:

my_str = "Of fice很幸运,她遇上了Python"
print(my_str[0])  # 输出:O
print(my_str[10])  # 输出:她

我想你应该看懂了,我们在字符串后面加上一个中括号,中括号里写你要访问第几个元素,“my_str[0]”表示访问字符串my_str的第0个元素,即字母“O”。你睡觉前可以偷偷从0开始数一下,看第10个元素是不是命中注定的“她”。

中括号里代表位置的数字,我们称之为下标或者索引,所以我下面提到的“位置”“下标”“索引”其实都是同一个意思。注意,字符串里每一个字符都对应一个索引,包括标点符号和空格,所以一个长度为n的字符串的最大索引应该是n-1,如果超过索引的最大值,程序就会报“IndexError: string index out of range”的错误,你也可以自己用代码验证一下。

在写程序的时候也不可能去数字符串的长度啊,万一那个字符串有几万个字符怎么办?不要担心,我们可以通过len()函数获取字符串长度,把变量名写进括号里就会返回该字符串的长度了。另外你可以想一下,如果我们访问的是字符串中0这个索引有没有可能会报错?有可能的,如果该字符串是一个空字符串,则不存在第0个元素。所谓空字符串就是没有任何字符的字符串,下面的变量a、b、c、d、e都是空字符串,但f和g不是,因为f里面有一个空格,g里面有一个换行符,毕竟空格和换行符都是字符。代码如下:

a = str()
b = ''
c = ""
d = ''''''
e = """"""
f = ' '
g = '\n'
print(len(a), len(b), len(c), len(d), len(e))  # 输出:0 0 0 0 0
print(len(f), len(g))  # 输出: 1 1

2.2.5 字符串切片

通过索引我们能访问元素,但每访问一个元素就写一个位置是不是太麻烦了。如果你想一次访问多个元素,我们可以使用切片,为了让你不被“切片”这个高大上的词吓跑,我们先看看切片的用法。代码如下:

my_str = "Of fice很幸运,她遇上了Python"
# 为了方便你数数,顺便列出几个字符的下标
# "Of fice很幸运,她遇上了Python"
# "012345 6 7 8 9"
print("my_str长度:", len(my_str))  # 输出:my_str长度: 20
print(my_str[0:20:])  # 输出:Of fice很幸运,她遇上了Python
print(my_str[6:9])  # 输出:很幸运
print(my_str[6:])  # 输出:很幸运,她遇上了Python
print(my_str[:9])  # 输出:Of fice很幸运
print(my_str[6:-1])  # 输出:很幸运,她遇上了Pytho
print(my_str[8:5:-1])  # 输出:运幸很
print(my_str[0::2])  # 输出:Ofc很运她上Pto

切片的用法很简单,字符串的中括号里最多可以有三个参数,分别是开始位置、结束位置、步长,都是用半角冒号分隔。开始位置和结束位置都是下标,从0开始数,开始位置对应的元素包含在切片中,结束位置对应的元素不包含在切片中,比如说字符串my_str =“Off i ce很幸运,她遇上了Python”,索引6对应的是汉字“很”,索引9对应的是逗号“,”,而my_str[6:9]得到的是“很幸运”而不是“很幸运,”,这种现象我们称之为“左闭右开”,“闭”是取得到,“开”是取不到(即取到的是前一个字符“运”)。步长的默认值是1,即每一个字符都取,如果指定步长为2,就会隔一个字符取一次,如果指定步长为3,那就隔两个字符取一次,比如说my_str[0:20]、my_str[0:20:]、my_str[0:20:1]三种写法效果是一样的,都会取所有字符,而my_str[0:20:2]得到的结果是“Ofc很运她上Pto”,my_str[0:20:3]得到的结果是“Oi很,上yo”。

再说一下切片的三个参数的取值,可以分为3种情况。

(1)只要是整数都可以,包括正数和负数。对于正数的情况我们都理解了,符合我们的生活常识,就不再多说。负数就是从后往前取值,比如说my_str[6:-1],开始索引是6,结束索引是-1,表示从第6个索引开始一直取到倒数第一个索引,结果是“很幸运,她遇上了Pytho”,因为左闭右开的原则,最后一个字符“n”取不到。再举一个例子,my_str[8:5:-1]的步长是-1,即从后往前取,索引8和5分别对应的字符是“运”和“e”,因为左闭右开,最终取到的是“运幸很”。

(2)三个参数都可以省略不写。当不写开始索引,则是从第0个索引开始;当不写结束索引,则是取后面的全部字符;当不写步长,则步长为1。举个例子,my_str[6:]表示从索引6开始取完后面的所有字符,得到的结果是“很幸运,她遇上了Python”。当三个参数全都不写,my_str[::]得到的效果与my_str一样,即代表整个字符串。

(3)当索引超出范围不会报错,若取不到任何元素则返回空字符串。前面我们说了,通过索引访问字符串,当索引对应的元素不存在则会报“string index out of range”的错误导致程序终止运行,比如说长度是20的my_str,你一意孤行要访问第100个元素my_str[100]肯定会报错的。但是如果你使用切片是不会报错的,比如说my_str[6:100]会返回从第6个索引开始的后面的所有字符。如果是开始索引和结束索引都超出范围也不会报错,比如说my_str[99:100]会返回一个空字符串。

2.2.6 查询元素

前面我们讲了如何通过下标访问字符串的元素,包括单个元素和多个元素,如果我想查询某个元素在不在字符串里该怎么办呢?str对象提供了index()和f i nd()两个方法,用法是“对象.方法名()”,注意点和括号都是半角的,后面讲到的其他方法中的标点符号也是类似的用法。示例代码如下:

my_str = "Of fice很幸运,她遇上了Python"
print(my_str.index("很"))  # 输出:6
print(my_str.index("很幸运"))  # 输出:6
print(my_str. find("很"))  # 输出:6
print(my_str. find("很幸运"))  # 输出:6

观察一下上面的代码,我们主要得到两个信息:一个是index()和f i nd()用法和结果一样;另一个是当查询多个字符的位置时它会返回第一个字符的位置。上面查询的都是字符串里存在的字符,如果字符不存在,index()和f i nd()这两个方法就不一样了。代码如下:

my_str = "Of fice很幸运,她遇上了Python"
print(my_str. find("爱"))  # 输出:-1
print(my_str.index("爱"))  # 报错:ValueError: substring not found

如果被查询的字符不在字符串里,f i nd()会返回-1,而index则是直接报“ValueError:substring not found”的错误,程序也会因报错导致终止执行。

2.2.7 拼接字符串

如果你有多个字符串,想要把它们合并为一个字符串,我们可以直接将二者相加。代码如下:

my_str1 = "Of fice很幸运"
my_str2 = ","
my_str3 = "她遇上了Python"
my_str = my_str1 + my_str2 + my_str3
print(my_str)  # 输出:Of fice很幸运,她遇上了Python

没想到除了数字类型,字符串也是可以相加的吧?其实字符串还可以相乘,比如说有一个字符串“*-”,我想让它重复出现10遍,则乘以10即可。代码如下:

char = "*-" * 10
print(char)  # 输出:*-*-*-*-*-*-*-*-*-*-

关于连接字符串,str对象还提供了一个join()方法,比如说,我想在字符串的每一个元素中间都加上其他元素,比如都加上一个空格,可以这么写:

my_str = "Of fice很幸运,她遇上了Python"
my_str2 = " ".join(my_str)
print(my_str2)# 输出:O f f i c e 很 幸 运 ,她 遇 上 了 P y t h o n

2.2.8 类型转换

相加的对象如果是数字类型则会进行算术运算,如果是字符类型则会直接拼接字符串,如果是数字类型加上一个字符串类型会是什么样的呢?程序会直接报错的。如果你一定要尝试,不妨对其中一个对象进行类型转换,可以通过str()、int()、f l oat()分别把其他类型强转成字符串、整型、浮点型。代码如下:

num = 520
print(type(num), num)  # 输出:<class 'int'> 520
# int类型转换成str类型
num = str(num)
print(type(num), num)  # 输出: <class 'str'> 520
# str类型转换成int类型
num = int(num)
print(type(num), num)  # 输出:<class 'int'> 520
# int类型转换成 float类型
num_ float = float(num)
print(type(num_ float), num_ float)  # 输出:<class ' float'> 520.0
# int类型转换成 float类型
print(int(66.88))  # 输出:66

使用这些方法可以随意转换以上三种类型,前提是长得像才能转换成功,比如说字符串“520”可以转换成int或f l oat类型,但是字符串“我很帅”就不能转成int或f l oat,程序会报错的,你可以鼓起勇气试一下。为了防止把字符串转换成int类型的时候报错,转换之前最好看一下它是不是可以转换成int类型,我们可以通过字符串对象的isdigit()方法判断,如果能转成init则返回True,不能的话就会返回False,等以后我们学到了条件判断就可以控制是否要转换了,现在先记住这个方法。代码如下:

my_str = "520"
is_digit = my_str.isdigit()
print(is_digit)  # 输出:True
print("a1".isdigit())  # 输出:False

另外,如果是f l oat类型数据转换成int类型数据,则会丢掉小数部分只保留整数。以后我们学到的更多数据类型也可以使用这种方法进行转换,但是能不能转换还是取决于该种类型是否支持转换。现在知道了类型转换之后,可以试一下使用字符串参与算术运算了。代码如下:

print(1 + 2)  # 输出:3
print(1 + int("2"))  # 输出:3
print( float("1") + float("2"))  # 输出:3.0

2.2.9 替换字符串

如果你想更换字符串里的某些字符,可以通过str对象的replace()方法,该方法可以传入三个参数,第一个是原字符串,第二个是目标字符串,第三个是替换次数,如果不写替换次数就是全部替换,比如说我们把字符串my_str里的“遇”替换成“爱”,对比全部替换和替换一次的效果。代码如下:

my_str = "Of fice很幸运,她遇上了Python,命运让他们相遇"
new_str = my_str.replace("遇", "爱")
print(new_str)  # 输出 Of fice很幸运,她爱上了Python,命运让他们相爱
new_str2 = my_str.replace("遇", "爱", 1)
print(new_str2)  # 输出:Of fice很幸运,她爱上了Python,命运让他们相遇

注意替换字符串并不是改变原来的字符串,而是返回一个新的字符串。

2.2.10 大小写转换

如果你的字符串里有字母,可以使用str对象的lower()和upper()方法轻松进行大小写转换。代码如下:

my_str = "Of fice很幸运,她遇上了Python"
new_str = my_str.lower()
print(new_str)  # 输出:of fice很幸运,她遇上了python
print(my_str.upper())  # 输出:OFFICE很幸运,她遇上了PYTHON

2.2.11 分割字符串

如果你想把字符串按照某个或某些字符进行分割,可以使用str对象的split()和rsplit()方法,这两个方法用法一样,区别是前者从左往右分割,后者是从右往左分割。split()和rsplit()的参数是一样的,第一个参数是按什么字符串分割,第二个参数是最大分割次数,如果不指定最大分割次数则是分割所有。把字符串my_str按中文逗号进行分割,代码如下:

my_str = "Of fice很幸运,她遇上了Python,命运让他们相遇"
print(my_str.split(",", 1))
# 输出:['Of fice很幸运', '她遇上了Python,命运让他们相遇']
print(my_str.split(","))
# 输出:['Of fice很幸运', '她遇上了Python', '命运让他们相遇']
print(my_str.rsplit(",", 1))
# 输出:['Of fice很幸运,她遇上了Python', '命运让他们相遇']

可以看到分割后得到的是一个列表,我们将在下一节学习列表,先简单知道列表是多个元素的集合,每个元素用半角逗号分开。my_str = “Off i ce很幸运,她遇上了Python,命运让他们相遇”,my_str.split(“,”, 1)是指把字符串my_str按中文逗号从左边分割1次,则得到“Off i ce很幸运”和“她遇上了Python,命运让他们相遇”两部分。因为my_str中有两个中文逗号,所以如果不指定最大分割次数则应该得到三个部分。

2.2.12 格式化字符串

所谓的格式化字符串其实就是把一些变量填充到字符串中,让字符串有一个统一的格式,有点像套模板。举一个例子,有一个字符串是“{}很幸运,她遇上了{}”,大括号表示待填写的数据,如果我有很多数据都按照这种形式填充,就相当于把这些数据格式化了。str()对象有一个format()方法用来格式化字符串,看一下代码演示:

my_str = "{}很幸运,她遇上了{}"
name1 = "Of fice"
name2 = "Python"
print(my_str)  # 输出:{}很幸运,她遇上了{}
print(my_str.format(name1, name2))  # 输出:Of fice很幸运,她遇上了Python
print(my_str.format(name1))  # 报错

看了上面的代码,我想你应该已经理解什么是格式化了,简单来说字符串中每个大括号都是一个坑,后面使用数据把那些坑填了。但是你要注意,如果待格式化的字符串有多个大括号,但你填充的数据数量不够,程序是会报错的。另外我们还注意到,format()方法里的数据是按从左到右的顺序一个一个填进待格式化的字符串的大括号里的,如果不想按顺序也是可以的,只要你在字符串的大括号里写上使用format()里的第几个参数,比如说第一个大括号和第三个大括号使用format()里的第1个参数,第二个大括号使用第0个参数,代码如下:

my_str = "{1}也很幸运,他遇上了{0},所以{1}很开心"
print(my_str.format("Of fice", "Python"))
# 输出:Python也很幸运,他遇上了Of fice,所以Python很开心

虽然可以指定位置,但是如果字符串很长,变量也很多,在看代码的时候还要一个一个去数每个位置对应的变量是什么,就会很麻烦,但你只要在字符串前面加上一个字母“f”就可以指明哪个大括号被哪个变量填充了。代码如下:

name1 = "Of fice"
name2 = "Python"
my_str = f"{name2}也很幸运,他遇上了{name1},所以{name2}很开心"
print(my_str)
# 输出: Python也很幸运,他遇上了Of fice,所以Python很开心

这种在字符串前面加上“f”的写法比format()方法显得更加简洁和直接,但是注意这种用法是Python3.6才开始支持的,即Python3.5和更早的版本不能用,所以从兼容性的角度来说,format()更好,因为即使在旧版本的Python环境代码也能正常执行。

其实还有一种比较经典的写法,几乎所有的高级语言都支持,那就是使用占位符,Python中用“%”连接待格式化字符串和数据,代码如下:

my_str = "%d年后,%s遇到了%s"
print(my_str % (5, "Of fice", "Python"))
# 输出:5年后,Of fice遇到了Python

代码中的%d代表用整型填充,%s代表用字符串填充,类似的还有%f,表示用浮点型填充,还有一些占位符就不列举了,感兴趣的朋友可以在网上搜一下。还需要补充的是%f是可以指定小数个数的,比如说“%.2f”(注意不要漏掉整数前面的小数点)就是填充的时候只保留两位小数。如果想输出“%”这个字符,那么就使用“%%”表示一个百分号。代码如下:

print("%.2f是圆周率的近似值" % 3.1415926)  # 输出:3.14是圆周率的近似值
print("增长比例高达%d%%" % 33.3)  # 输出:增长比例高达33%