【Python基础】第十七课:正则表达式

正则表达式,re

Posted by x-jeff on December 3, 2020

本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.正则表达式

re模块使Python语言拥有全部的正则表达式功能。

1.1.re.search()

re.search扫描整个字符串并返回第一个成功的匹配。

1
2
3
4
5
6
import re
a = '23123'
re.search('1', a)#输出为:<re.Match object; span=(2, 3), match='1'>
re.search('23', a)#输出为:<re.Match object; span=(0, 2), match='23'>
re.search('1', a).span()#输出为:(2, 3)
re.search('23', a).span()#输出为:(0, 2)

如果想匹配多个字符中的任意一个,可使用[]。返回[]中任意一个字符的第一个成功匹配:

1
2
re.search('[0123]', a)#输出为:<re.Match object; span=(0, 1), match='2'>
re.search('[13]', a)#输出为:<re.Match object; span=(1, 2), match='3'>

如果匹配失败,则返回None,例如:

1
re.search('4', a)#输出为:None

此外,re.search('[0123456789]', a)可简写为re.search('[0-9]', a)或者re.search('\d', a)\d表示任意数字的匹配)。

同样的,除了对数字的查找,也可以对字母进行查找:

1
2
3
4
5
6
7
a = 'cdbapa'
re.search('a', a)#输出为:<re.Match object; span=(3, 4), match='a'>
re.search('[abcdefghijklmnopqrstuvwxyz]', a)#输出为:<re.Match object; span=(0, 1), match='c'>
re.search('[a-z]', a)#输出为:<re.Match object; span=(0, 1), match='c'>
b = 'A'
re.search('[a-z]', b)#输出为:None
re.search('[a-zA-Z]', b)#输出为:<re.Match object; span=(0, 1), match='A'>

re.search('[a-zA-Z0-9]', b)可简写为:re.search('\w', b)

1
2
3
b = 'A4'
re.search('[a-zA-Z0-9]', b)#输出为:<re.Match object; span=(0, 1), match='A'>
re.search('\w', b)#输出为:<re.Match object; span=(0, 1), match='A'>

使用{n}可以限定匹配n个字符:

1
2
3
4
5
a = 'abc'
re.search('\w'), a)#输出为:<re.Match object; span=(0, 1), match='a'>
re.search('\w{2}'), a)#输出为:<re.Match object; span=(0, 2), match='ab'>
re.search('\w{3}'), a)#输出为:<re.Match object; span=(0, 3), match='abc'>
re.search('[acabdf]{2}', a)#输出为:<re.Match object; span=(0, 2), match='ab'>

更进一步的,使用{a,b}表示在满足最小匹配a个字符的前提下,返回其不超过b的最大匹配:

1
2
3
4
5
a = 'abc'
b = 'abcdefg'
re.search('\w{4,15}', b)#输出为:<re.Match object; span=(0, 7), match='abcdefg'>
re.search('\w{4,6}', b)#输出为:<re.Match object; span=(0, 6), match='abcdef'>
re.search('\w{4,15}', a)#输出为:None

当只限制最小匹配为1个及以上时,可以使用符号+,即相当于{1,inf}。例如写成如下形式:

1
2
3
4
b = 'abcdefg'
re.search('\w+', b)#输出为:<re.Match object; span=(0, 7), match='abcdefg'>
b = 'a'
re.search('\w+', b)#输出为:<re.Match object; span=(0, 1), match='a'>

类似的,使用符号*相当于{0,inf}

1
2
3
b = ''
re.search('\w+', b)#输出为:None
re.search('\w*', b)#输出为:<re.Match object; span=(0, 0), match=''>

举个实际应用的例子,查找电话号码:

1
2
a = 'my phone is 134-1234-1234'
re.search('\d{3}-\d{4}-\d{4}', a)#输出为:<re.Match object; span=(12, 25), match='134-1234-1234'>

如果电话簿中同时还存在另外一种电话的记录形式,即没有符号-,例如:'my phone is 13412341234'。此时便需要我们可以同时处理以上两种号码的记录方式:

1
2
3
4
5
6
7
8
a = 'my phone is 134-1234-1234'
b = 'my phone is 13412341234'
#方法一:
re.search('\d{3}-{0,1}\d{4}-{0,1}\d{4}', a)#输出为:<re.Match object; span=(12, 25), match='134-1234-1234'>
re.search('\d{3}-{0,1}\d{4}-{0,1}\d{4}', b)#输出为:<re.Match object; span=(12, 23), match='13412341234'>
#方法二:
re.search('\d{3}-?\d{4}-?\d{4}', a)#输出为:<re.Match object; span=(12, 25), match='134-1234-1234'>
re.search('\d{3}-?\d{4}-?\d{4}', b)#输出为:<re.Match object; span=(12, 23), match='13412341234'>

?即表示{0,1}

❗️汇总一下正则表达式中可能会用到的一些符号:

  1. .:比对除换行外的任意字符。
  2. ^:比对字符串开始。
  3. $:比对字符串结尾。
  4. *:比对0个或多个由前面正则表达式定义的片段,贪婪方式。
  5. +:比对1个或多个由前面正则表达式定义的片段,贪婪方式。
  6. ?:比对0个或1个由前面正则表达式定义的片段,贪婪方式。
  7. *?+???:非贪婪版本的*+?(尽可能少的比对)。
  8. [...]:比对[]内字符集中的任意一个字符。
  9. (...):比对()内的表达式,也表示一个群组。
  10. (?P<id>...):类似(…),但该组同时得到一个id,可以在后面的模式中引用。

1.2.re.match()

re.match()尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。

⚠️re.match()re.search()的区别:re.match()只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search()匹配整个字符串,直到找到一个匹配。

假如我们现在要使用re.match()来匹配邮箱:

1
2
email = 'david@largidata.com'
re.match('\w+@\w+', email)#输出为:<re.Match object; span=(0, 15), match='david@largidata'>

如果我们想要将davidlargidata两个信息单独提取出来,则我们需要使用()对其进行分组并使用group()groups()进行提取:

1
2
3
4
5
6
7
8
m = re.match('(\w+)@(\w+)', email)
print(m)#输出为:<re.Match object; span=(0, 15), match='david@largidata'>
print(m.group())#输出为:david@largidata
print(m.group(1))#输出为:david
print(m.group(2))#输出为:largidata
print(m.groups())#输出为:('david', 'largidata')
print(m.groups()[0])#输出为:david
print(m.groups()[1])#输出为:largidata

如果需要将.com也提取出来,则需要加上对句点.的匹配,但是.在正则表达式中已经有了特殊的含义,因此对句点.的匹配可以使用\.

1
2
3
4
5
m = re.match('(\w+)@([a-z\.]+)', email)
print(m.groups())#输出为:('david', 'largidata.com')
#m可简写为:
m = re.match('(\w+)@(.+)', email)
print(m.groups())#输出为:('david', 'largidata.com')

再举一个数字匹配的例子:

1
2
3
digit = '1999.5'
m = re.match('(\d+)\.(\d+)', digit)
print(m.groups())#输出为:('1999', '5')

如果现在我们要提取名字中的first name和last name,根据以上学过的知识,我们可以这样实现:

1
2
3
4
name = 'David Chiu'
m = re.match('(\w+) (\w+)', name)
print(m.group(1))#输出为:David
print(m.group(2))#输出为:Chiu

此外,我们可以使用(?P<id>...)为其添加标签:

1
2
3
4
name = 'David Chiu'
m = re.match('(?P<first_name>\w+) (?P<last_name>\w+)', name)
print(m.group('first_name'))#输出为:David
print(m.group('last_name'))#输出为:Chiu

^$的使用:^从第一个字符开始匹配;$从倒数第一个字符开始匹配。

1
2
3
4
5
6
s = '123 abc'
re.search('^[0-9]', s)#输出为:<re.Match object; span=(0, 1), match='1'>
re.search('^[2-9]', s)#输出为:None
re.search('^[a-z]', s)#输出为:None
re.search('[0-9]$', s)#输出为:None
re.search('[a-z]$', s)#输出为:<re.Match object; span=(6, 7), match='c'>

2.在DataFrame上使用正则表达式

以以下数据为例(数据下载链接):

用正则表达式从“户型”中抽取“室”、“厅”、“厨”、“卫”:

1
df[['室', '厅', '厨', '卫']] = df['户型'].str.extract('(\d+)室(\d+)厅(\d+)厨(\d+)卫', expand=False)

3.代码地址

  1. 正则表达式

4.参考资料

  1. Python 正则表达式(菜鸟教程)
  2. python 正则表达式的^和$符号使用技巧