字符串是所有编程语言在项目开发过程中涉及最多的一个内容。大部分项目的运行结果,都需要以文本的形式展示给客户,比如财务系统的总账报表,电子游戏的比赛结果,火车站的列车时刻表等。
5.1 字符串常用操作
在Python开发过程中,为了实现某项功能,经常需要对某些字符串进行特殊处理,如拼接字符串、截取字符串、格式化字符串等。
5.1.1 拼接字符串
使用“+”运算符可完成对多个字符串的拼接,“+”运算符可以连接多个字符串并产生一个字符串对象。
例如,定义两个字符串,一个保存英文版的名言,另一个用于保存中文版的名言,然后使用“+”运算符连接,代码如下:
mot_en = 'Remembrance is a form of meeting. Forgetfulness is a form of freedom.'
mot_cn = '记忆是一种相遇。遗忘是一种自由。'print(mot_en + '——' + mot_cn)
字符串不允许直接与其他类型的数据拼接
解决该问题,可以将整数转换为字符串,然后以拼接字符串的方法输出该内容。
5.1.2 计算字符串的长度
由于不同的字符所占字节数不同,所以要计算字符串的长度,需要先了解各字符所占的字节数。在Python中,数字、英文、小数点、下划线和空格占一个字节;一个汉字可能会占2~4个字节,占几个字节取决于采用的编码。汉字在GBK/GB2312编码中占2个字节,在UTF-8/unicode编码中一般占用3个字节(或4个字节)
在Python中,提供了len()函数计算字符串的长度,语法格式如下:
len(string)
其中,string用于指定要进行长度统计的字符串。
在默认的情况下,通过len()函数计算字符串的长度时,不区分英文、数字和汉字,所有字符都按一个字符计算。
5.1.3 截取字符串
由于字符串也属于序列,所以要截取字符串,可以采用切片方法实现。通过切片方法截取字符串的语法格式如下:
string[start : end : step]
参数说明:
string:表示要截取的字符串。
start:表示要截取的第一个字符的索引(包括该字符),如果不指定,则默认为0。
end:表示要截取的最后一个字符的索引(不包括该字符),如果不指定则默认为字符串的长度。
step:表示切片的步长,如果省略,则默认为1,当省略该步长时,最后一个冒号也可以省略。
说明:字符串的索引同序列的索引是一样的,也是从0开始,并且每个字符占一个位置。
例如,定义一个字符串,然后应用切片方法截取不同长度的子字符串,并输出,代码如下:
str1 = '人生苦短,我用Python!' # 定义字符串
substr1 = str1[1] # 截取第2个字符
substr2 = str1[5:] # 从第6个字符截取
substr3 = str1[:5] # 从左边开始截取5个字符
substr4 = str1[2:5] # 截取第3个到第5个字符print('原字符串:',str1)print(substr1 + '\n' + substr2 + '\n' + substr3 + '\n' + substr4)
注意:在进行字符串截取时,如果指定的索引不存在,则会抛出异常。
要解决该问题,可以采用try…except语句捕获异常。
5.1.4 分割、合并字符串
在Python中,字符串对象提供了分割和合并字符串的方法。分割字符串是把字符串分割为列表,而合并字符串是把列表合并为字符串,分割字符串和合并字符串可以看作是互逆操作。
1.分割字符串
字符串对象的split()方法可以实现字符串分割,也就是把一个字符串按照指定的分隔符切分为字符串列表。该列表的元素中,不包括分隔符。split()方法的语法格式如下:
str.split(sep, maxsplit)
参数说明:
str:表示要进行分割的字符串。
sep:用于指定分隔符,可以包含多个字符,默认为None,即所有空字符(包括空格、换行“\n”、制表符“\t”等)。
maxsplit:可选参数,用于指定分割的次数,如果不指定或者为-1,则分割次数没有限制,否则返回结果列表的元素个数,个数最多为maxsplit+1。
返回值:分隔后的字符串列表。该列表的元素为以分隔符为界限分割的字符串(不含分隔符),当该分隔符前面(或与前一个分隔符之间)无内容时,将返回一个空字符串元素。说明:在split()方法中,如果不指定sep参数,那么也不能指定maxsplit参数。
例如,定义一个保存明日学院网址的字符串,然后应用split()方法根据不同的分隔符进行分割,代码如下:
str1 = '明 日 学 院 官 网 >>> www.mingrisoft.com'print('原字符串:',str1)
list1 = str1.split() # 采用默认分隔符进行分割
list2 = str1.split('>>>') # 采用多个字符进行分割
list3 = str1.split('.') # 采用.号进行分割
list4 = str1.split(' ',4) # 采用空格进行分割,并且只分割前4个
print(str(list1) + '\n' + str(list2) + '\n' + str(list3) + '\n' + str(list4))
list5 = str1.split('>') # 采用>进行分割print(list5)
上面的代码在执行后,将显示以下内容:
原字符串: 明 日 学 院 官 网 >>> www.mingrisoft.com
['明', '日', '学', '院', '官', '网', '>>>', 'www.mingrisoft.com']
['明 日 学 院 官 网 ', ' www.mingrisoft.com']
['明 日 学 院 官 网 >>> www', 'mingrisoft', 'com']
['明', '日', '学', '院', '官 网 >>> www.mingrisoft.com']
['明 日 学 院 官 网 ', ' ', ' ', ' www.mingrisoft.com']
说明:在使用split()方法时,如果不指定参数,默认采用空白符进行分割,这时无论有几个空格或者空白符都将作为一个分隔符进行分割。
2.合并字符串
合并字符串与拼接字符串不同,它会将多个字符串采用固定的分隔符连接在一起。
合并字符串可以使用字符串对象的join()方法实现,语法格式如下:strnew = string.join(iterable)
参数说明:
strnew:表示合并后生成的新字符串。
string:字符串类型,用于指定合并时的分隔符。
iterable:可迭代对象,该迭代对象中的所有元素(字符串表示)将被合并为一个新的字符串。string作为边界点分割出来。
5.1.5 检索字符串
在Python中,字符串对象提供了很多应用于字符串查找的方法,这里主要介绍以下几种方法。
1.count()方法
count()方法用于检索指定字符串在另一个字符串中出现的次数。如果检索的字符串不存在,则返回0,否则返回出现的次数。其语法格式如下:
str.count(sub[, start[, end]])
参数说明:
str:表示原字符串。
sub:表示要检索的子字符串。
start:可选参数,表示检索范围的起始位置的索引,如果不指定,则从头开始检索。
end:可选参数,表示检索范围的结束位置的索引,如果不指定,则一直检索到结尾。
2.find()方法
该方法用于检索是否包含指定的子字符串。如果检索的字符串不存在,则返回-1,否则返回首次出现该子字符串时的索引。其语法格式如下:str.find(sub[, start[, end]])
参数说明:
str:表示原字符串。
sub:表示要检索的子字符串。
start:可选参数,表示检索范围的起始位置的索引,如果不指定,则从头开始检索。
end:可选参数,表示检索范围的结束位置的索引,如果不指定,则一直检索到结尾。
说明:如果只是想要判断指定的字符串是否存在,可以使用in关键字实现。例如,上面的字符串str1中是否存在@符号,可以使用print('@' in str1),如果存在就返回True,否则返回False。另外,也可以根据find()方法的返回值是否大于-1来确定指定的字符串是否存在。
说明:Python的字符串对象还提供了rfind()方法,其作用与find()方法类似,只是从字符串右边开始查找。
3.index()方法
index()方法同find()方法类似,也是用于检索是否包含指定的子字符串。只不过如果使用index()方法,当指定的字符串不存在时会抛出异常。其语法格式如下:
str.index(sub[, start[, end]])
参数说明:
str:表示原字符串。
sub:表示要检索的子字符串。
start:可选参数,表示检索范围的起始位置的索引,如果不指定,则从头开始检索。
end:可选参数,表示检索范围的结束位置的索引,如果不指定,则一直检索到结尾。
说明:Python的字符串对象还提供了rindex()方法,其作用与index()方法类似,只是从右边开始查找。
4.startswith()方法
startswith()方法用于检索字符串是否以指定子字符串开头。如果是则返回True,否则返回False。该方法语法格式如下:
str.startswith(prefix[, start[, end]])
参数说明:
str:表示原字符串。
prefix:表示要检索的子字符串。
start:可选参数,表示检索范围的起始位置的索引,如果不指定,则从头开始检索。
end:可选参数,表示检索范围的结束位置的索引,如果不指定,则一直检索到结尾。
5.endswith()方法
endswith()方法用于检索字符串是否以指定子字符串结尾。如果是则返回True,否则返回False。该方法语法格式如下:
str.endswith(suffix[, start[, end]])
参数说明:
str:表示原字符串。
suffix:表示要检索的子字符串。
start:可选参数,表示检索范围的起始位置的索引,如果不指定,则从头开始检索。
end:可选参数,表示检索范围的结束位置的索引,如果不指定,则一直检索到结尾。
5.1.6 字母的大小写转换
在Python中,字符串对象提供了lower()方法和upper()方法进行字母的大小写转换,即可用于将大写字母转换为小写字母或者将小写字母转换为大写字母,
1.lower()方法
lower()方法用于将字符串中的大写字母转换为小写字母。如果字符串中没有需要被转换的字符,则将原字符串返回;否则将返回一个新的字符串,将原字符串中每个需要进行小写转换的字符都转换成等价的小写字符。字符长度与原字符长度相同。lower()方法的语法格式如下:
str.lower()
其中,str为要进行转换的字符串。
2.upper()方法
upper()方法用于将字符串中的小写字母转换为大写字母。如果字符串中没有需要被转换的字符,则将原字符串返回;否则返回一个新字符串,将原字符串中每个需要进行大写转换的字符都转换成等价的大写字符。新字符长度与原字符长度相同。upper()方法的语法格式如下:
str.upper()
5.1.7 去除字符串中的空格和特殊字符
用户在输入数据时,可能会无意中输入多余的空格,或在一些情况下,字符串前后不允许出现空格和特殊字符,此时就需要去除字符串中的空格和特殊字符。可以使用Python中提供的strip()方法去除字符串左右两边的空格和特殊字符,也可以使用lstrip()方法去除字符串左边的空格和特殊字符,使用rstrip()方法去除字符串中右边的空格和特殊字符。
说明:这里的特殊字符是指制表符\t、回车符\r、换行符\n等。
1.strip()方法
strip()方法用于去掉字符串左、右两侧的空格和特殊字符,语法格式如下:
str.strip([chars])
参数说明:
str:为要去除空格的字符串。
chars:为可选参数,用于指定要去除的字符,可以指定多个。如果设置chars为“@.”,则去除左、右两侧包括的“@”或“.”。如果不指定chars参数,默认将去除空格、制表符“\t”、回车符“\r”、换行符“\n”等。
2.lstrip()方法
lstrip()方法用于去掉字符串左侧的空格和特殊字符,语法格式如下:
str.lstrip([chars])
参数说明:
str:为要去除空格的字符串。
chars:为可选参数,用于指定要去除的字符,可以指定多个,如果设置chars为“@.”,则去除左侧包括的“@”或“.”。如果不指定chars参数,默认将去除空格、制表符“\t”、回车符“\r”、换行符“\n”等。
3.rstrip()方法
rstrip()方法用于去掉字符串右侧的空格和特殊字符,语法格式如下:
str.rstrip([chars])
参数说明:
str:为要去除空格的字符串。
chars:为可选参数,用于指定要去除的字符,可以指定多个,如果设置chars为“@.”,则去除右侧包括的“@”或“.”。如果不指定chars参数,默认将去除空格、制表符“\t”、回车符“\r”、换行符“\n”等。
5.1.8 格式化字符串
格式化字符串是指先制定一个模板,在这个模板中预留几个空位,然后再根据需要填上相应的内容。这些空位需要通过指定的符号标记(也称为占位符),而这些符号还不会显示出来。
1.使用“%”操作符
在Python中,要实现格式化字符串,可以使用“%”操作符,语法格式如下:'%[-][+][0][m][.n]格式化字符'%exp
参数说明:
-:可选参数,用于指定左对齐,正数前方无符号,负数前面加负号。
+:可选参数,用于指定右对齐,正数前方加正号,负数前方加负号。
0:可选参数,表示右对齐,正数前方无符号,负数前方加负号,用0填充空白处(一般与m参数一起使用)。
m:可选参数,表示占有宽度。
.n:可选参数,表示小数点后保留的位数。
格式化字符:用于指定类型。
exp:要转换的项。如果要指定的项有多个,需要通过元组的形式进行指定,但不能使用列表。
例如,格式化输出一个保存公司信息的字符串,代码如下:
template = '编号:%09d\t公司名称: %s \t官网: http://www.%s.com' # 定义模板
context1 = (7,'百度','baidu') # 定义要转换的内容1
context2 = (8,'明日学院','mingrisoft') # 定义要转换的内容2print(template%context1) # 格式化输出print(template%context2) # 格式化输出
说明:由于使用%操作符是早期Python中提供的方法,自从Python 2.6版本开始,字符串对象提供了format()方法对字符串进行格式化。现在一些Python社区也推荐使用这种方法。
2.使用字符串对象的format()方法
字符串对象提供了format()方法用于进行字符串格式化,语法格式如下:
str.format(args)
参数说明:
str:用于指定字符串的显示样式(即模板)。
args:用于指定要转换的项,如果有多项,则用逗号进行分隔。
下面重点介绍创建模板。在创建模板时,需要使用“{}”和“:”指定占位符,语法格式如下:{[index][:[[fill]align][sign][#][width][.precision][type]]}
参数说明:
index:可选参数,用于指定要设置格式的对象在参数列表中的索引位置,索引值从0开始。如果省略,则根据值的先后顺序自动分配。
fill:可选参数,用于指定空白处填充的字符。
align:可选参数,用于指定对齐方式(值为“<”时表示内容左对齐;值为“>”时表示内容右对齐;值为“=”时表示内容右对齐,只对数字类型有效,即将数字放在填充字符的最右侧,值为“^”时表示内容居中),需要配合width一起使用。
sign:可选参数,用于指定有无符号数(值为“+”表示正数加正号,负数加负号;值为“-”表示正数不变;负数加负号,值为空格表示正数加空格,负数加负号)。
#:可选参数,对于二进制数、八进制数和十六进制数,如果加上#,表示会显示0b/0o/0x前缀,否则不显示前缀。
width:可选参数,用于指定所占宽度。
.precision:可选参数,用于指定保留的小数位数。
type:可选参数,用于指定类型。
format()方法中常用的格式化字符如表所示。
说明:当一个模板中,出现多个占位符时,指定索引位置的规范需统一,即全部采用手动指定,或者全部采用自动。例如,定义“'我是数值:{:d},我是字符串:{1:s}'”模板是错误的。会抛出异常。
实例06 格式化不同的数值类型数据
在IDLE中创建一个名称为formatnum.py的文件,然后在该文件中将不同类型的数据进行格式化并输出,代码如下:
01 import math # 导入Python的数学模块
02 # 以货币形式显示
03 print('1251+3950的结果是(以货币形式显示):¥{:,.2f}元'.format(1251+3950))
04 print('{0:.1f}用科学计数法表示:{0:E}'.format(120000.1)) # 用科学计数法表示
05 print('π取5位小数:{:.5f}'.format(math.pi)) # 输出小数点后五位
06 print('{0:d}的16进制结果是:{0:#x}'.format(100)) # 输出十六进制数
07 # 输出百分比,并且不带小数08 print('天才是由 {:.0%} 的灵感,加上 {:.0%} 的汗水 。'.format(0.01,0.99))
运行实例,将显示如图所示的结果。
5.2 字符串编码转换
在Python中,有两种常用的字符串类型,分别为str和bytes。其中,str表示Unicode字符(ASCII或者其他);bytes表示二进制数据(包括编码的文本)。这两种类型的字符串不能拼接在一起使用。通常情况下,str在内存中以Unicode表示,一个字符对应若干个字节。但是如果在网络上传输,或者保存到磁盘上,就需要把str转换为字节类型,即bytes类型。说明:bytes类型的数据是带有b前缀的字符串(用单引号或双引号表示),例如,b'\xd2\xb0'和b'mr'都是bytes类型的数据。
str类型和bytes类型之间可以通过encode()和decode()方法进行转换,这两个方法是互逆的过程。
5.2.1 使用encode()方法编码
encode()方法为str对象的方法,用于将字符串转换为二进制数据(即bytes),也称为“编码”,其语法格式如下:
str.encode([encoding="utf-8"][,errors="strict"])
参数说明:
str:表示要进行转换的字符串。
encoding="utf-8":可选参数,用于指定进行转码时采用的字符编码,默认为UTF-8,如果想使用简体中文,也可以设置为gb2312。当只有这一个参数时,也可以省略前面的“encoding=”,直接写编码。
errors="strict":可选参数,用于指定错误处理方式,其可选择值可以是strict(遇到非法字符就抛出异常)、ignore(忽略非法字符)、replace(用“?”替换非法字符)或xmlcharrefreplace(使用XML的字符引用)等,默认值为strict。
说明:在使用encode()方法时,不会修改原字符串,如果需要修改原字符串,需要对其进行重新赋值。
5.2.2 使用decode()方法解码
decode()方法为bytes对象的方法用于将二进制数据转换为字符串,即将使用encode()方法转换的结果再转换为字符串,也称为解码”。语法格式如下:
bytes.decode([encoding="utf-8"][,errors="strict"])
参数说明:
bytes:表示要进行转换的二进制数据,通常是encode()方法转换的结果。
encoding="utf-8":可选参数,用于指定进行解码时采用的字符编码,默认为UTF-8,如果想使用简体中文,也可以设置为gb2312。当只有这一个参数时,也可以省略前面的“encoding=”,直接写编码。注意:在设置解码采用的字符编码时,需要与编码时采用的字符编码一致。
errors="strict":可选参数,用于指定错误处理方式,其可选择值可以是strict(遇到非法字符就抛出异常)、ignore(忽略非法字符)、replace(用“?”替换非法字符)或xmlcharrefreplace(使用XML的字符引用)等,默认值为strict。
说明:在使用decode()方法时,不会修改原字符串,如果需要修改原字符串,需要对其进行重新赋值。
5.3 正则表达式
在处理字符串时,经常会有查找符合某些复杂规则的字符串的需求。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
5.3.1 行定位符
行定位符就是用来描述字符串的边界,“^”表示行的开始,“$”表示行的结尾。如:
^tm
该表达式表示要匹配字符串tm的开始位置是行头,如“tm equal Tomorrow Moon”可以匹配,而“Tomorrow Moon equal tm”则不匹配。但如果使用:
tm$
后者可以匹配而前者不能匹配。如果要匹配的字符串可以出现在字符串的任意部分,那么可以直接写成下面的格式,这样两个字符串就都可以匹配了。
tm
5.3.2 元字符
除了前面介绍的元字符“^”和“$”外,正则表达式里还有更多的元字符,例如下面的正则表达式中就应用了元字符“\b”和“\w”。
\bmr\w*\b
上面的正则表达式用于匹配以字母mr开头的单词,先从某个单词开始处(\b),然后匹配字母mr,接着是任意数量的字母或数字 (\w*),最后单词结束处(\b)。该表达式可以匹配“mrsoft”“\nmr”和“mr123456”等,但不能与“amr”匹配。更多常用元字符如表所示。
5.3.3 限定符
在上面例子中,使用(\w*)匹配任意数量的字母或数字。如果想匹配特定数量的数字,该如何表示呢?正则表达式为我们提供了限定符(指定数量的字符)来实现该功能。如匹配8位QQ号可用如下表达式:
^\d{8}$
常用的限定符如表所示。
5.3.4 字符类
正则表达式查找数字和字母是很简单的,因为已经有了对应这些字符集合的元字符(如“\d”“\w”),但是如果要匹配没有预定义元字符的字符集合(比如元音字母 a, e, i, o, u ),应该怎么办?
很简单,只需要在方括号里列出它们就行了,像[aeiou]可以匹配任何一个英文元音字母,[.?!] 匹配标点符号(“.”“?”或“!”)。也可以轻松地指定一个字符范围,像“[0-9]”代表的含义与“\d”就是完全一致的:一位数字;同理,“[a-z0-9A-Z_]”完全等同于“\w”(如果只考虑英文的话)。说明:要想匹配给定字符串中任意一个汉字,可以使用“[\u4e00-\u9fa5]”;如果要匹配连续多个汉字,可以使用“[\u4e00-\u9fa5]+”。
5.3.5 排除字符
在5.3.4小节列出的是匹配符合指定字符集合的字符串。现在反过来,匹配不符合指定字符集合的字符串。正则表达式提供了“^”字符。这个元字符在5.3.1小节中出现过,表示行的开始。而这里将会放到方括号中,表示排除的意思。例如:
[^a-zA-Z]
该表达式用于匹配一个不是字母的字符。
5.3.6 选择字符
试想一下,如何匹配身份证号码?首先需要了解一下身份证号码的规则。身份证号码长度为15位或者18位。如果为15位时,则全为数字;如果为18位时,前17位为数字,最后一位是校验位,可能为数字或字符X。
在上面的描述中,包含着条件选择的逻辑,这就需要使用选择字符(|)来实现。该字符可以理解为“或”,匹配身份证的表达式可以写成如下方式:
(^\d{15}$)|(^\d{18}$)|(^\d{17})(\d|X|x)$
该表达式的意思是以匹配15位数字,或者18位数字,或者17位数字和最后一位。最后一位可以是数字,也可以是X或者x。
5.3.7 转义字符
正则表达式中的转义字符(\)和Python中的大同小异,都是将特殊字符(如“.”“?”“\”等)变为普通的字符。举一个IP地址的实例,用正则表达式匹配诸如“127.0.0.1”格式的IP地址。如果直接使用点字符,格式为:
[1-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}
这显然不对,因为“.”可以匹配一个任意字符。这时,不仅是127.0.0.1这样的IP,连127101011这样的字符串也会被匹配出来。所以在使用“.”时,需要使用转义字符(\)。修改后上面的正则表达式格式为:
[1-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}说明:括号在正则表达式中也算是一个元字符。
5.3.8 分组
通过5.3.6小节中的例子,相信读者已经对小括号的作用有了一定的了解。小括号字符的第一个作用就是可以改变限定符的作用范围,如“|”“*”“^”等。例如下面的表达式中包含小括号。
(six|four)th
这个表达式的意思是匹配单词sixth或fourth,如果不使用小括号,那么就变成了匹配单词six和fourth了。
小括号的第二个作用是分组,也就是子表达式。如(\.[0-9]{1,3}){3},就是对分组(\.[0-9]{1,3})进行重复操作。
5.3.9 在Python中使用正则表达式语法
在Python中使用正则表达式时,是将其作为模式字符串使用的。例如,将匹配不是字母的一个字符的正则表达式表示为模式字符串,可以使用下面的代码:'[^a-zA-Z]'
而如果将匹配以字母m开头的单词的正则表达式转换为模式字符串,则不能直接在其两侧添加引号定界符,例如,下面的代码是不正确的。
\bm\w*\b'
而是需要将其中的“\”进行转义,转换后的结果为:
'\\bm\\w*\\b'
由于模式字符串中可能包括大量的特殊字符和反斜杠,所以需要写为原生字符串,即在模式字符串前加r或R。例如,上面的模式字符串采用原生字符串表示为:
r'\bm\w*\b'说明:在编写模式字符串时,并不是所有的反斜杠都需要进行转换,例如,前面编写的正则表达式“^\d{8}$”中的反斜杠就不需要转义,因为其中的\d并没有特殊意义。不过,为了编写方便,本书中的正则表达式都采用原生字符串表示。
5.3 正则表达式
在处理字符串时,经常会有查找符合某些复杂规则的字符串的需求。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
5.3.1 行定位符
行定位符就是用来描述字符串的边界,“^”表示行的开始,“$”表示行的结尾。如:
^tm
该表达式表示要匹配字符串tm的开始位置是行头,如“tm equal Tomorrow Moon”可以匹配,而“Tomorrow Moon equal tm”则不匹配。但如果使用:
tm$
后者可以匹配而前者不能匹配。如果要匹配的字符串可以出现在字符串的任意部分,那么可以直接写成下面的格式,这样两个字符串就都可以匹配了。
tm
5.3.2 元字符
除了前面介绍的元字符“^”和“$”外,正则表达式里还有更多的元字符,例如下面的正则表达式中就应用了元字符“\b”和“\w”。
\bmr\w*\b
上面的正则表达式用于匹配以字母mr开头的单词,先从某个单词开始处(\b),然后匹配字母mr,接着是任意数量的字母或数字 (\w*),最后单词结束处(\b)。该表达式可以匹配“mrsoft”“\nmr”和“mr123456”等,但不能与“amr”匹配。更多常用元字符如表所示。
5.3.3 限定符
在上面例子中,使用(\w*)匹配任意数量的字母或数字。如果想匹配特定数量的数字,该如何表示呢?正则表达式为我们提供了限定符(指定数量的字符)来实现该功能。如匹配8位QQ号可用如下表达式:
^\d{8}$
常用的限定符如表所示。
5.3.4 字符类
正则表达式查找数字和字母是很简单的,因为已经有了对应这些字符集合的元字符(如“\d”“\w”),但是如果要匹配没有预定义元字符的字符集合(比如元音字母 a, e, i, o, u ),应该怎么办?
很简单,只需要在方括号里列出它们就行了,像[aeiou]可以匹配任何一个英文元音字母,[.?!] 匹配标点符号(“.”“?”或“!”)。也可以轻松地指定一个字符范围,像“[0-9]”代表的含义与“\d”就是完全一致的:一位数字;同理,“[a-z0-9A-Z_]”完全等同于“\w”(如果只考虑英文的话)。说明:要想匹配给定字符串中任意一个汉字,可以使用“[\u4e00-\u9fa5]”;如果要匹配连续多个汉字,可以使用“[\u4e00-\u9fa5]+”。
5.3.5 排除字符
在5.3.4小节列出的是匹配符合指定字符集合的字符串。现在反过来,匹配不符合指定字符集合的字符串。正则表达式提供了“^”字符。这个元字符在5.3.1小节中出现过,表示行的开始。而这里将会放到方括号中,表示排除的意思。例如:
[^a-zA-Z]
该表达式用于匹配一个不是字母的字符。
5.3.6 选择字符
试想一下,如何匹配身份证号码?首先需要了解一下身份证号码的规则。身份证号码长度为15位或者18位。如果为15位时,则全为数字;如果为18位时,前17位为数字,最后一位是校验位,可能为数字或字符X。
在上面的描述中,包含着条件选择的逻辑,这就需要使用选择字符(|)来实现。该字符可以理解为“或”,匹配身份证的表达式可以写成如下方式:
(^\d{15}$)|(^\d{18}$)|(^\d{17})(\d|X|x)$
该表达式的意思是以匹配15位数字,或者18位数字,或者17位数字和最后一位。最后一位可以是数字,也可以是X或者x。
5.3.7 转义字符
正则表达式中的转义字符(\)和Python中的大同小异,都是将特殊字符(如“.”“?”“\”等)变为普通的字符。举一个IP地址的实例,用正则表达式匹配诸如“127.0.0.1”格式的IP地址。如果直接使用点字符,格式为:
[1-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}
这显然不对,因为“.”可以匹配一个任意字符。这时,不仅是127.0.0.1这样的IP,连127101011这样的字符串也会被匹配出来。所以在使用“.”时,需要使用转义字符(\)。修改后上面的正则表达式格式为:
[1-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}说明:括号在正则表达式中也算是一个元字符。
5.3.8 分组
通过5.3.6小节中的例子,相信读者已经对小括号的作用有了一定的了解。小括号字符的第一个作用就是可以改变限定符的作用范围,如“|”“*”“^”等。例如下面的表达式中包含小括号。
(six|four)th
这个表达式的意思是匹配单词sixth或fourth,如果不使用小括号,那么就变成了匹配单词six和fourth了。
小括号的第二个作用是分组,也就是子表达式。如(\.[0-9]{1,3}){3},就是对分组(\.[0-9]{1,3})进行重复操作。
5.3.9 在Python中使用正则表达式语法
在Python中使用正则表达式时,是将其作为模式字符串使用的。例如,将匹配不是字母的一个字符的正则表达式表示为模式字符串,可以使用下面的代码:'[^a-zA-Z]'
而如果将匹配以字母m开头的单词的正则表达式转换为模式字符串,则不能直接在其两侧添加引号定界符,例如,下面的代码是不正确的。
\bm\w*\b'
而是需要将其中的“\”进行转义,转换后的结果为:'\\bm\\w*\\b'
由于模式字符串中可能包括大量的特殊字符和反斜杠,所以需要写为原生字符串,即在模式字符串前加r或R。例如,上面的模式字符串采用原生字符串表示为:
r'\bm\w*\b'说明:在编写模式字符串时,并不是所有的反斜杠都需要进行转换,例如,前面编写的正则表达式“^\d{8}$”中的反斜杠就不需要转义,因为其中的\d并没有特殊意义。不过,为了编写方便,本书中的正则表达式都采用原生字符串表示。
5.4 使用re模块实现正则表达式操作
Python提供了re模块,用于实现正则表达式的操作。在实现时,可以使用re模块提供的方法(如search()、match()、findall()等)进行字符串处理,也可以先使用re模块的compile()方法将模式字符串转换为正则表达式对象,然后再使用该正则表达式对象的相关方法来操作字符串。
re模块在使用时,需要先应用import语句引入,具体代码如下:
import re
5.4.1 匹配字符串
1.使用match()方法进行匹配
match()方法用于从字符串的开始处进行匹配,如果在起始位置匹配成功,则返回Match对象,否则返回None。其语法格式如下:
re.match(pattern, string, [flags])
参数说明:
pattern:表示模式字符串,由要匹配的正则表达式转换而来。
string:表示要匹配的字符串。
flags:可选参数,表示标志位,用于控制匹配方式,如是否区分字母大小写。
import re
pattern = r'mr_\w+' # 模式字符串
string = 'MR_SHOP mr_shop' # 要匹配的字符串
match = re.match(pattern,string,re.I) # 匹配字符串,不区分大小写print(match) # 输出匹配结果
string = '项目名称MR_SHOP mr_shop'
match = re.match(pattern,string,re.I) # 匹配字符串,不区分大小写print(match) # 输出匹配结果
执行结果如下:
<_sre.SRE_Match object; span=(0, 7), match='MR_SHOP'>
None
从上面的执行结果中可以看出,字符串“MR_SHOP”以“mr_”开头,将返回一个Match对象,而字符串“项目名称MR_SHOP”没有以“mr_”开头,将返回“None”。这是因为match()方法从字符串的开始位置开始匹配,当第一个字母不符合条件时,则不再进行匹配,直接返回None。
Match对象中包含了匹配值的位置和匹配数据。其中,要获取匹配值的起始位置可以使用Match对象的start()方法;要获取匹配值的结束位置可以使用end()方法;通过span()方法可以返回匹配位置的元组;通过string属性可以获取要匹配的字符串。例如下面的代码:import re
pattern = r'mr_\w+' # 模式字符串
string = 'MR_SHOP mr_shop' # 要匹配的字符串
match = re.match(pattern,string,re.I) # 匹配字符串,不区分大小写print('匹配值的起始位置:',match.start())print('匹配值的结束位置:',match.end())print('匹配位置的元组:',match.span())print('要匹配的字符串:',match.string)print('匹配数据:',match.group())
执行结果如下:
匹配值的起始位置: 0
匹配值的结束位置: 7
匹配位置的元组: (0, 7)
要匹配字符串: MR_SHOP mr_shop
匹配数据: MR_SHOP
2.使用search()方法进行匹配
search()方法用于在整个字符串中搜索第一个匹配的值,如果匹配成功,则返回Match对象,否则返回None。search()方法的语法格式如下:
re.search(pattern, string, [flags])
参数说明:
pattern:表示模式字符串,由要匹配的正则表达式转换而来。
string:表示要匹配的字符串。
flags:可选参数,表示标志位,用于控制匹配方式,如是否区分字母大小写。常用的标志如表5.5所示。
例如,搜索第一个以“mr_”开头的字符串,不区分字母大小写,代码如下:import re
pattern = r'mr_\w+' # 模式字符串
string = 'MR_SHOP mr_shop' # 要匹配的字符串
match = re.search(pattern,string,re.I) # 搜索字符串,不区分大小写print(match) # 输出匹配结果
string = '项目名称MR_SHOP mr_shop'
match = re.search(pattern,string,re.I) # 搜索字符串,不区分大小写print(match) # 输出匹配结果
执行结果如下:
<_sre.SRE_Match object; span=(0, 7), match='MR_SHOP'>
<_sre.SRE_Match object; span=(4, 11), match='MR_SHOP'>
从上面的运行结果中可以看出,search()方法不仅仅是在字符串的起始位置搜索,其他位置有符合的匹配也可以进行搜索。
3.使用findall()方法进行匹配
findall()方法用于在整个字符串中搜索所有符合正则表达式的字符串,并以列表的形式返回。如果匹配成功,则返回包含匹配结构的列表,否则返回空列表。findall()方法的语法格式如下:
re.findall(pattern, string, [flags])
参数说明:
pattern:表示模式字符串,由要匹配的正则表达式转换而来。
string:表示要匹配的字符串。
flags:可选参数,表示标志位,用于控制匹配方式,如是否区分字母大小写。常用的标志如表5.5所示。
例如,搜索以“mr_”开头的字符串,代码如下:import re
pattern = r'mr_\w+' # 模式字符串
string = 'MR_SHOP mr_shop' # 要匹配的字符串
match = re.findall(pattern,string,re.I) # 搜索字符串,不区分大小写print(match) # 输出匹配结果
string = '项目名称MR_SHOP mr_shop'
match = re.findall(pattern,string) # 搜索字符串,区分大小写print(match) # 输出匹配结果
执行结果如下:
['MR_SHOP', 'mr_shop']
['mr_shop']
如果在指定的模式字符串中,包含分组,则返回与分组匹配的文本列表。例如,import re
pattern = r'[1-9]{1,3}(\.[0-9]{1,3}){3}' # 模式字符串
str1 = '127.0.0.1 192.168.1.66' # 要配置的字符串
match = re.findall(pattern,str1) # 进行模式匹配print(match)
上面代码的执行结果如下:
['.1', '.66']
从上面的结果中可以看出,并没有得到匹配的IP地址,这是因为在模式字符串中出现了分组,所以得到的结果是根据分组进行匹配的结果,即“(\.[0-9]{1,3})”匹配的结果。如果想获取整个模式字符串的匹配,可以将整个模式字符串使用一对小括号进行分组,然后在获取结果时,只取返回值列表的每个元素(是一个元组)的第1个元素。代码如下:import re
pattern = r'([1-9]{1,3}(\.[0-9]{1,3}){3})' # 模式字符串
str1 = '127.0.0.1 192.168.1.66' # 要配置的字符串
match = re.findall(pattern,str1) # 进行模式匹配for item in match:
print(item[0])
执行结果如下:
127.0.0.1
192.168.1.66
5.4.2 替换字符串
sub()方法用于实现字符串替换,语法格式如下:
re.sub(pattern, repl, string, count, flags)
参数说明:
pattern:表示模式字符串,由要匹配的正则表达式转换而来。
repl:表示替换的字符串。
string:表示要被查找替换的原始字符串。
count:可选参数,表示模式匹配后替换的最大次数,默认值为0,表示替换所有的匹配。
flags:可选参数,表示标志位,用于控制匹配方式,如是否区分字母大小写。
例如,隐藏中奖信息中的手机号码,代码如下:import re
pattern = r'1[34578]\d{9}' # 定义要替换的模式字符串
string = '中奖号码为:84978981 联系电话为:13611111111'
result = re.sub(pattern,'1XXXXXXXXXX',string) # 替换字符串print(result)
执行结果如下:
中奖号码为:84978981 联系电话为:1XXXXXXXXXX
实例 替换出现的危险字符
import re # 导入Python的re模块
pattern = r'(黑客)|(抓包)|(监听)|(Trojan)' # 模式字符串
about = '我是一名程序员,我喜欢看黑客方面的图书,想研究一下Trojan。\n'
sub = re.sub(pattern, '@_@', about) # 进行模式替换
print(sub)
about = '我是一名程序员,我喜欢看计算机网络方面的图书,喜欢开发网站。'
sub = re.sub(pattern, '@_@', about) # 进行模式替换
print(sub)
5 .4.3 使用正则表达式分割字符串
split()方法用于实现根据正则表达式分割字符串,并以列表的形式返回。其作用同字符串对象的split()方法类似,所不同的就是分割字符由模式字符串指定。split()方法的语法格式如下:
re.split(pattern, string, [maxsplit], [flags])
参数说明:
pattern:表示模式字符串,由要匹配的正则表达式转换而来。
string:表示要匹配的字符串。
maxsplit:可选参数,表示最大的拆分次数。
flags:可选参数,表示标志位,用于控制匹配方式,如是否区分字母大小写。常用的标志如表5.5所示。
例如,从给定的URL地址中提取出请求地址和各个参数,代码如下:import re
pattern = r'[?|&]' # 定义分割符
url = 'http://www.mingrisoft.com/login.jsp?username="mr"&pwd="mrsoft"'
result = re.split(pattern,url) # 分割字符串print(result)
执行结果如下:
['http://www.mingrisoft.com/login.jsp', 'username="mr"', 'pwd="mrsoft"']
实例输出被@的好友名称(应用正则表达式)
import re
str1 = '@明日科技 @扎克伯格 @俞敏洪'
pattern = r'\s*@'
list1 = re.split(pattern,str1) # 用空格和@或单独的@分割字符串
print('您@的好友有:')
for item in list1:
ifitem != "": # 输出不为空的元素
print(item) # 输出每个好友名
5.5实战
1.判断车牌归属地
根据车牌号码可以知道该车辆的归属地,本实战将实现输出指定车牌的归属地功能
代码如下:
carNum = ['粤a10000','京a12345','沪a12345']
for i in range(len(carNum)):
print('第'+str(i+1)+'张车牌号码')
print(carNum[i])
if carNum[i][0]=='粤':
print('广东')
if carNum[i][0]=='京':
print('北京')
if carNum[i][0]=='沪':
print('上海')
2.显示实时天气预报
应用字符串的format()方法格式化输出实时天气预报。
# 天气预报列表
weathers=[('2018年4月17日','晴','20℃~7℃','微风转西风3~4级'),
('08:00','晴','13℃','微风'),
('12:00', '晴', '19℃', '微风'),
('16:00', '晴', '18℃', '西风3~4级'),
('20:00', '晴', '15℃', '西风3~4级'),
('00:00', '晴', '12℃', '微风'),
('04:00', '晴', '9℃', ' 微风')
]
# 循环天气预报列表
for weather in weathers:
# 循环输出列表内容 在 第二个参数前添加'天气预报:'
print('{} 天气预报:{} {} {}'.format(weather[0],weather[1],weather[2],weather[3]))
3.模拟微信抢红包(提示:本实例实现时需要应用生成随机数的random模块和支持十进制浮点运算的decimal模块)
# -*- coding: utf-8 -*-
# decimal意思为十进制,这个模块提供了十进制浮点运算支持。
from decimal import Decimal
# 提供了随机方法
import random
print('——————————模拟微信抢红包——————————')
total = input('请输入要装入红包的总金额(元):')
num = input('请输入红包的个数(个):')
min = 0.01 # 每个人最少能收到 0.01 元
# 创建红包列表
money_list = []
# 转换红包金额为十进制 方便后期计算
total = Decimal(total)
# 转换红包个数为十进制 方便后期计算
num = Decimal(num)
# 转换最小红包数为十进制 方便后期计算
min = Decimal(str(min))
# 判断红包金额是否 大于每个 红包个数*每个人最少获得的0.01
if total > num * min:
# 根据红包个数进行循环 重1开始 所以随机出来的会少1个红包
for i in range(1, int(num)):
# 根据循环到的红包个数 判断随机安全上限 不至于红包m没有没人最少0.01
safe_total = (total - (num - i) * min)
# 随机出 获取红包金额
temp_min = min *100 # 随机的最小值
temp_max = int(safe_total * 100) # 随机的最大值
money = temp_min/100 if temp_min > temp_max else (Decimal(random.randint(temp_min, temp_max)))/100
# 重置总金额 减去随机出的金额
total -= money
# 添加随机出的金额到 红包列表
money_list.append(money)
#保存最后一个元素到红包数组 不足红包数量
money_list.append(total)
# 随机打乱列表顺序
random.shuffle(money_list)
for x in range(len(money_list)): # 输出结果
print('第'+str(x+1)+'个红包:'+str(money_list[x])+'元')
5.6 小结
本章首先对常用的字符串操作技术进行了详细的讲解,其中拼接、截取、分割、合并、检索和格式化字符串等都是需要重点掌握的技术;然后介绍了正则表达式的基本语法,以及Python中如何应用re模块实现正则表达式匹配等技术。