跳至主要內容

Day 9:Python 字符串和正则介绍总结

AI悦创原创Python全栈60天精通之路Python全栈60天精通之路大约 10 分钟...约 3118 字

你好,我是悦创。

字符串无所不在,字符串的处理也是最常见的操作。

今天,一起学习字符串处理相关的操作。主要包括:

  • 基本的字符串操作
  • 高级字符串操作之正则部分

1. 基本操作

1.1 反转字符串

In [1]: s = "python"

# 方法1    
In [6]: rs = ''.join(reversed(s))

In [7]: rs
Out[7]: 'nohtyp'
#方法2
In [5]: s[::-1]
Out[5]: 'nohtyp'

1.2 字符串切片操作

生成 1 到 15 的序列,并在满足条件的索引处,替换为 javapython

In [15]: java, python = "java", "python"

In [16]: jl, pl = len(java), len(python)

# 索引 2、5、8 处替换为 Java 字符
# 索引 4 处替换为 Python 字符

In [17]: [str(java[i % 3 * jl:] + python[i % 5 * pl:] or i) for i in range(1, 10)]

输出结果,如下图:

1.3 join 串联字符串

用下划线 _ 连接字符串 mystr:

n [8]: mystr = ['I', 'love', 'Python']

In [9]: res = '_'.join(mystr)

In [10]: res
Out[10]: 'I_love_Python'

1.4 分割字符串

根据指定字符或字符串,分割一个字符串时,使用方法 split。

join 和 split 可看做一对互逆操作:

In [23]: 'I_love_python'.split('_')
Out[23]: ['I', 'love', 'python']

1.5 替换

字符串替换,使用 replace 方法。

如下字符串,小写的 o 全部替换为大写的 O:

In [14]: s = 'i love python'.replace('o', 'O')

In [15]: s
Out[15]: 'i lOve pythOn'

1.6 子串判断

判断 a 串是否为 b 串的子串。

方法 1,使用 in:

In [16]: a = 'our'
In [17]: b = 'flourish'

In [18]: r = True if a in b else False

In [19]: r
Out[19]: True

方法 2,使用方法 find,返回字符串 b 中匹配子串 a 的最小索引:

In [16]: a = 'our'
In [17]: b = 'flourish'

In [21]: b.find(a)
Out[21]: 2

1.7 去空格

清洗字符串时,位于字符串开始和结尾的空格,有时需要去掉,strip 方法能实现。

如下字符串,使用 strip,清理字符串开头和结尾的空格和制表符。

In [24]: a = '  \tI love python  \b\n'

In [25]: a
Out[25]: '  \tI love python  \x08\n'

In [26]: a.strip()
Out[26]: 'I love python  \x08'

1.8 字符串的字节长度

encode 方法对字符串编码后:

In [12]: def str_byte_len(mystr):
    ...:     mystr_bytes = mystr.encode('utf-8')
    ...:     return (len(mystr_bytes))


In [13]: str_byte_len('i love python') 
Out[13]: 13

2. 正则表达式

字符串封装的方法,处理一般的字符串操作,还能应付。但是,稍微复杂点的字符串处理任务,需要靠正则表达式,简洁且强大。

首先,导入所需要的模块 re:

import re

首先,认识常用的元字符:

  • . 匹配除 "\n" 和 "\r" 之外的任何单个字符。
  • ^ 匹配字符串开始位置
  • $ 匹配字符串中结束的位置
  • * 前面的原子重复 0 次、1 次、多次
  • ? 前面的原子重复 0 次或者 1 次
  • + 前面的原子重复 1 次或多次
  • {n} 前面的原子出现了 n 次
  • {n,} 前面的原子至少出现 n 次
  • {n,m} 前面的原子出现次数介于 n-m 之间
  • ( ) 分组,输出需要的部分

再认识常用的通用字符:

  • \s 匹配空白字符
  • \w 匹配任意字母/数字/下划线
  • \W 和小写 w 相反,匹配任意字母/数字/下划线以外的字符
  • \d 匹配十进制数字
  • \D 匹配除了十进制数以外的值
  • [0-9] 匹配一个 0~9 之间的数字
  • [a-z] 匹配小写英文字母
  • [A-Z] 匹配大写英文字母

正则表达式,常会涉及到以上这些元字符或通用字符,下面通过 14 个细分的与正则相关的小功能,讨论正则表达式。

2.1 search 第一个匹配串

使用正则模块,search 方法,找出子串第一个匹配位置。

In [31]: s = 'i love python very much'

In [32]: pat = 'python'

In [33]: r = re.search(pat,s)

In [34]: r.span()
Out[34]: (7, 13)

2.2 match 与 search 不同

正则模块中,match、search 方法匹配字符串不同

具体不同:

  • match 在原字符串的开始位置匹配
  • search 在字符串的任意位置匹配

原字符串:

In [105]: s = 'flourish'

寻找模式串 our,使用 match 方法:

In [106]: recom = re.compile('our')

In [107]: recom.match(s) # 返回 None,找不到匹配

使用 search 方法:

In [109]: res = recom.search(s)

In [110]: res.span()
Out[110]: (2, 5) # OK, 匹配成功,our 在原字符串的起始索引为 2

那么,什么字符串才能使用 match 方法匹配到 our?

比如,字符串 ourselves,ours 才能 match 到 our。

2.3 finditer 匹配迭代器

使用正则模块,finditer 方法,返回所有子串匹配位置的迭代器。

通过返回的对象 re.Match,使用它的方法 span 找出匹配位置。

In [35]: s = '山东省潍坊市青州第1中学高三1班'

In [36]: pat = '1'

In [37]: r = re.finditer(pat,s)

In [38]: for i in r:
    ...:     print(i)

<re.Match object; span=(9, 10), match='1'>
<re.Match object; span=(14, 15), match='1'>

2.4 findall 所有匹配

正则模块,findall 方法能查找出子串的所有匹配。

原字符串 s:

In [39]: s = '一共20行代码运行时间13.59s'

目标查找出所有所有数字:通用字符 \d 匹配一位数字 [0-9],+ 表示匹配数字前面的一个字符 1 次或多次。

In [40]: pat = r'\d+'

In [41]: r = re.findall(pat,s)

In [42]: print(r)
['20', '13', '59']

返回一个列表,找到三个数字 20、13、59,没有达到预期,期望找到 20、13.59。

因此,需要修改正则表达式。

2.5 案例:匹配浮点数和整数

  • ? 表示前一个字符匹配 0 或 1 次
  • .? 表示匹配小数点(.)0 次或 1 次。

匹配浮点数和整数,第一版正则表达式:r'\d+\.?\d+',图形化演示,此正则表达式的分解演示:

In [43]: s = '一共20行代码运行时间13.59s'

In [44]: pat = r'\d+\.?\d+'

In [45]: r = re.findall(pat,s)

In [46]: r
Out[46]: ['20', '13.59']

上面的正则表达式 r'\d+\.?\d+',能适配所有情况吗?

In [47]: s = '一共2行代码运行时间1.66s'

In [48]: pat = r'\d+\.?\d+'

In [49]: r = re.findall(pat,s)

In [50]: r
Out[50]: ['1.66']

观察结果,没有匹配到数字 2。

正则难点之一,需要考虑全面、足够细心,才可能写出准确无误的正则表达式。

出现问题原因:r'\d+\.?\d+',后面的 \d+ 表示至少有一位数字,因此,整个表达式至少会匹配两位数。

修复问题,重新优化正则表达式,将最后的 + 后修改为 *,表示匹配前面字符 0 次、1 次或多次。

最终正则表达式为:r'\d+\.?\d*',正则分解图:

In [51]: pat = r'\d+\.?\d*'

In [52]: r = re.findall(pat,s)

In [53]: r
Out[53]: ['2', '1.66']

2.6 案例:匹配正整数

案例:写出匹配所有正整数的正则表达式。

如果这样写:^\d*$,会匹配到 0,所以不准确。

如果这样写:^[1-9]*,会匹配 1. 串中 1,不是完全匹配,体会 $ 的作用。

正确写法:^[1-9]\d*$,正则分解图:

In [62]: s = [-16, 1.5, 11.43, 10, 5]
In [63]: pat = r'^[1-9]\d*$'
In [70]: [i for i in s if re.match(pat,str(i))]
Out[70]: [10, 5]

2.7 re.I 忽略大小写

re.I 是方法的可选参数,表示忽略大小写。

如下,找出字符串中所有字符 t 或 T 的位置,不区分大小写。

In [71]: s = 'That'

In [72]: pat = r't'

In [73]: r = re.finditer(pat,s,re.I)

In [79]: for i in r:
    ...:     print(i.span())
    ...:
(0, 1)
(3, 4)

2.8 split 分割单词

正则模块中 split 函数强大,能够处理复杂的字符串分割任务。

如果一个规则简单的字符串,直接使用字符串,split 函数。

如下字符串,根据分割符 \t 分割:

In [91]: s = 'id\tname\taddress'

In [92]: s.split('\t')
Out[92]: ['id', 'name', 'address']

但是,对于分隔符复杂的字符串,split 函数就无能为力。

如下字符串,可能的分隔符有 ,;\t|

In [100]: s = 'This,,,   module ; \t   provides|| regular ; '

正则字符串为:[,\s;|]+\s 匹配空白字符,正则分解图,如下:

In [102]: words = re.split('[,\s;|]+',s)

In [103]: words
Out[103]: ['This', 'module', 'provides', 'regular', '']

2.9 sub 替换匹配串

正则模块,sub 方法,替换匹配到的子串:

In [113]: content="hello 12345, hello 456321"

In [114]: pat=re.compile(r'\d+') #要替换的部分

In [115]: m=pat.sub("666",content)

In [116]: m
Out[116]: 'hello 666, hello 666'

2.10 compile 预编译

如果要用同一匹配模式,做很多次匹配,可以使用 compile 预先编译串。

案例:从一系列字符串中,挑选出所有正浮点数。

正则表达式为:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$,字符 a|b 表示 a 串匹配失败后,才执行 b 串,正则分解图见下:

首先,生成预编译对象 rec:

In [80]: s = [-16,'good',1.5, 0.2, -0.1, '11.43', 10, '5e10']

In [81]: rec = re.compile(r'^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$')

下面直接使用 rec,匹配列表中的每个元素,不用每次都预编译正则表达式,效率更高。

In [84]: [ i for i in s if rec.match(str(i))]
Out[84]: [1.5, 0.2, '11.43']

2.11 贪心捕获

正则模块中,根据某个模式串,匹配到结果。

待爬取网页的部分内容如下所示,现在想要提取 <div> 标签中的内容。

In [125]: content = '''
     ...:     <h>ddedadsad</h>
     ...:     <div>graph</div>
     ...:    <div>math</div>'''

如果正则匹配串写做 <div>.*</div>

In [118]: result = re.findall(r'<div>.*</div>',content)

In [119]: result
Out[119]: ['<div>graph</div>bb<div>math</div>']

返回的结果:

'<div>graph</div>bb<div>math</div>' 

如果我们不想保留字符串最开始的 <div> 和结尾的 </div>,那么,就需要使用一对 () 去捕获。

正则匹配串修改为:<div>(.*)</div>,只添加一对括号。

In [120]: result = re.findall(r'<div>(.*)</div>',content)

In [121]: result
Out[121]: ['graph</div>bb<div>math']
  • 看到结果中已经没有开始的 <div>,结尾的 </div> 仅使用一对括号,便成功捕获到我们想要的部分。
  • (.*) 表示捕获任意多个字符,尽可能多地匹配字符,也被称为贪心捕获
  • (.*) 的正则分解图如下所示,. 表示匹配除换行符外的任意字符。

2.12 非贪心捕获

观察上面返回的结果 ['graph</div>bb<div>math'],如果只想要得到两个 <div></div> 间的内容,该怎么写正则表达式?

相比 (.*),仅多添加一个 ?,匹配串为 (.*?)

In [125]: content = '''
     ...:     <h>ddedadsad</h>
     ...:     <div>graph</div>
     ...:    <div>math</div>'''

In [126]: result = re.findall(r'<div>(.*?)</div>',content)

In [127]: result
Out[127]: ['graph', 'math']

终于得到 2 个 <div> 对间的内容。

这种匹配模式串 (.*?),被称为非贪心捕获。正则图中,红色虚线表示非贪心匹配。

通过上述例子和分析,体会贪心和非贪心匹配的不同之处。

小结

今天学习:

  • 8 个字符串相关功能,如 join 连接串、split 分割串、replace 做简单替换。
  • 正则表达式中常见的元字符和通用字符。
  • 10 个正则表达式核心要点。
  • 2 个正则案例。

欢迎关注我公众号:AI悦创,有更多更好玩的等你发现!

公众号:AI悦创【二维码】

AI悦创·编程一对一

AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh

C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh

方法一:QQopen in new window

方法二:微信:Jiabcdefh

上次编辑于:
贡献者: AndersonHJB
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度