机器学习之正则表达式

机器学习之正则表达式

正则表达式(Regular Expression, RE)就是一组定义某种搜索模式(pattern)的字符。

1、原始字符串(r)

  • 原始字符串(raw string)是所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符。

1)对于没有转义字符(反斜杠\)时,原始字符串和普通字符串是一样的

print('hello') #输出hello
print(r'hello') #输出hello

2)对于有转义字符(反斜杠\)时,原始字符串和普通字符串是不一样的

print('\bhello') #输出hello,\b匹配字符串首尾的空字符
print(r'\bhello') #输出\bhello

如果想保持字符串的原始模样,可以使用原始字符串r。

2、特殊字符( [] * + ? {} | () . ^ $ \)

  • python中包含一些自带的特殊字符,分类如下:
  • 表示集合:[]
  • 表示次数:* + ? {}
  • 表示并列:|
  • 用于提取:()
  • 用于转义:. ^ $ \

定义一个函数looking,当在string中没有找到pat时,返回“空”,反之打印所有符合模式的子字符串。

import re
def looking(pat, strs):
	return '空' if re.search(pat, strs) is None else re.findall(pat, strs)

2.1、集合字符(中括号 [])

中括号中包含了一些列字符的集合:

  • 明确字符:[abc] 匹配字符a,b,c
  • 范围字符:[a-z] 匹配字符a-z
  • 补集字符:[^6] 匹配除了6以外的字符

1)明确字符
匹配括号里任意一个字符

pat=r'[abc]'
print(looking(pat, 'a'))
print(looking(pat, 'ac'))
print(looking(pat, 'cba'))
print(looking(pat, 'seven'))
# 输出
['a']
['a', 'c']
['c', 'b', 'a']

2)范围字符
在[]中加入-即可设定范围,如:

  • [a-e]=[abcde]
  • [1-4]=[1234]
  • [a-ep]=[abcdep]
  • [0-38]=[01238]
print(looking(r'[a-ep]', 'person'))
print(looking(r'[0-38]', '666'))
#输出
['p', 'e']

3)补集字符
在[]中加入^即可表示出去后面的字符集,如:

  • [^abc] 除去a,b,c的字符
  • [^123] 除去1,2,3的字符
print( looking(r'[^abc]', 'baba') )
print( looking(r'[^abc]', 'steven') )
print( looking(r'[^123]', '456') )
print( looking(r'[^123]', '1+2=3') )
#输出['s', 't', 'e', 'v', 'e', 'n']
['4', '5', '6']
['+', '=']

2.2 字符次数(* + ? {})

上面的模式有缺陷,即只能匹配单个字符,在实际使用中需要带有次数的匹配字符。
1)贪婪模式

  • * 表示后面跟0个或多个字符
  • +表示后面跟1个或多个字符
  • ?表示后面跟0个或1个字符

2)非贪婪模式

  • *? 表示后面跟0个或多个字符,但只取第一个;
  • +? 表示后面跟1个或多个字符,但只取第一个;
  • ?? 表示后面跟0个或1个字符,但只取第一个;
'''星号*:匹配u字符0次或多次'''
pat=r'colou*r'
print(looking(pat, 'color'))
print(looking(pat, 'colour'))
print(looking(pat, 'colouuuur'))
# 输出
['color']
['colour']
['colouuuur']
'''加号+:匹配u字符1次或多次'''
pat=r'colou+r'
print(looking(pat, 'color'))
print(looking(pat, 'colour'))
print(looking(pat, 'colouuuur'))
# 输出['colour']
['colouuuur']
'''问号?:匹配u字符0次或1次'''
pat=r'colou?r'
print(looking(pat, 'color'))
print(looking(pat, 'colour'))
print(looking(pat, 'colouuuur'))
# 输出
['color']
['colour']

有的时候一个句子里会有重复的字符,假如是 > 字符,如果我们要匹配这个>,到底在哪一个 > 就停止了呢?

这个就是贪婪(greedy)模式非贪婪(non-greedy)模式的区别,让我们来看个例子。

heading=r'<h1>TITLE</h1>'

如果采用模式 <.+>,那么我们要获取的就是以 < 开头,以 > 结尾,中间有 1 个或多个字符的字符串。其中(.) 字符,它是一个通配符,可以代表任何除新行 (\n)之外的其他字符。

pat=r'<.+>'
print(looking(pat, heading))
#结果
['<h1>TITLE</h1>']

结果如上,获取的字符串确实以 < 开头,以 > 结尾,但是仔细看下,其实在 heading[3] 出也是 >,为什么没有匹配到它而是匹配到最后一个 > 呢?

原因就是上面用了贪婪模式,即在整个表达式匹配成功的前提下,尽可能多的匹配。那么其对立的非贪婪模式,就是在整个表达式匹配成功的前提下,尽可能少的匹配。
实现非贪婪模式只需在最后加一个 ? 字符,代码如下:

pat =  r'<.+?>'
print( looking(pat, heading) )
#输出
['<h1>', '</h1>']

3) 大括号{}:非常明确要匹配的字符出现几次,如

  • 中国的手机号位数是 13 位,n = 13;
  • 密码需要 8 位以上,n ≥ 8;
  • 公众号文章标题长度不能超过 64,n ≤ 64;
  • 用户名需要在 8 到 16 位之间,8 ≤ n ≤ 16;

我们可以设定具体的上界或(和)下界,使得代码更加有效也更好读懂,规则如下:

  • {n} 左边的字符串是否出现 n 次;
  • {n, } 左边的字符串是否出现大于等于 n 次;
  • {, n} 左边的字符串是否出现小于等于 n 次;
  • {n, m} 左边的字符串是否出现在 n 次和 m 次之间;

如下例子所示:

s = 'a11bbb2222ccccc'
print( looking(r'[a-z]{1}', s) )
print( looking(r'[0-9]{2,}', s) )
print( looking(r'[a-z]{,5}', s) ) #匹配五个以下的 a 到 z 小写字母,当然也包括零个,因此结果包含那些空字符。
print( looking(r'[0-9]{2,4}', s) )
#输出
['a', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'c']
['11', '2222']
['a', '', '', 'bbb', '', '', '', '', 'ccccc', '']
['11', '2222']

上面都是贪婪模式,当然也有其对应的非贪婪模式,但只有 {n, m}? 有意义。上面的模式对于前一个字符重复 m 到 n 次,并且取尽可能少的情况。比如在字符串’sssss’中,s{2,4} 会匹配 4 个 s,但 s{2,4}? 只匹配 2 个 s。

2.3 并列字符(竖线|)

字符集合问题解决了,字符次数问题解决了,如果现在面临的问题着是匹配 A 或 B 其中一个呢?用垂线 | 字符,A|B,如果 A 匹配了,则不再查找 B,反之亦然。

匹配句子中like或love一词的模式:

pat = r'like|love'
print( looking(pat, 'like you') )
print( looking(pat, 'love you') )
#输出
['like']
['love']

2.4 提取字符(小括号())

首先定义“beat 的第三人称,过去式,过去分词和现在进行式”的模式,获取 beat 加正确后缀的所有单词。

pat = r'beat(s|ed|en|ing)'

print( looking(pat, 'beats') )
print( looking(pat, 'beated') )
print( looking(pat, 'beaten') )
print( looking(pat, 'beating') )
#输出
['s']
['ed']
['en']
['ing']

我们将出现在 () 里面的后缀都获取出来了。

但其实这不是我们想要的,我们想把带着后缀的 beat 给获取出来。那么只有在最外面再加一层 (),模式如下。

pat = r'(beat(s|ed|en|ing))'

print( looking(pat, 'beats') )
print( looking(pat, 'beated') )
print( looking(pat, 'beaten') )
print( looking(pat, 'beating') )
#输出
[('beats', 's')]
[('beated', 'ed')]
[('beaten', 'en')]
[('beating', 'ing')]

其可视图如下,我们发现 Group 2 嵌套在 Group 1 里面。
在这里插入图片描述
现在带着后缀的 beat 已经获取出来了,在列表中每个元组的第一个元素,但如果不想要后缀(即元组的第二个元素),可以用下面的模式。

在 () 中最前面加入 ?:。(?:) 代表只匹配不获取(non-capturing),结果看上去非常自然。


pat = r'(beat(?:s|ed|en|ing))'
print( looking(pat, 'beats') )
print( looking(pat, 'beated') )
print( looking(pat, 'beaten') )
print( looking(pat, 'beating') )
#输出
['beats']
['beated']
['beaten']
['beating']

2.5 转义字符((点.),(托字符^),(美元符$),(反斜杠\))

  • 转义字符:能够转换自身含义的字符,如(点.),(托字符^),(美元符$),(反斜杠\)
  • 点.:通配符,可以匹配除新行(newline)外的其他任意字符。
# 定义含有1个或多个非新行字符
pat=r'.+'
print(looking(pat, 'a'))
print(looking(pat, 'b1'))
print(looking(pat, 'a@9'))
print(looking(pat, '$ 9_fZ'))
print(looking(pat, '9z_\t\r\n'))
#输出
['a']
['b1']
['a@9']
['$ 9_fZ']
['9z_\t\r']
#除了换行符\n没匹配到,其他字符都匹配出来了
  • 托字符^:表示字符串开头
# 定义以s 开头字符串”的模
pat=r'^s[\w]*' #\w匹配任意数字、大小写字母和下划线,等价于[a-zA-Z0-9_]
print(looking(pat, 'son'))
print(looking(pat, 'shot'))
print(looking(pat, 'come'))
#结果
['son']
['shot']
  • 美元符$:表示字符串结尾
#定义以 s 结尾字符串”的模式
pat=r'[\w]*s$'
print( looking(pat, 'yes') )
print( looking(pat, 'mess') )
print( looking(pat, 'come') )
#结果
['yes']
['mess']
  • 反斜杠\:可对特殊字符进行转义,也可以对普通字符进行转义
    1)将特殊字符转成自身含义:用 \ 作用在 ^ . \ 等身上,代表乘方\ ^、小数点\ . 和除号\ \;
    2)将自身字符转成特殊含义:用 \ 作用在 w d n 等身上,代表数字字母下划线 \w、数字 \d 和新行 \n。

2.6 反斜杠\,特殊符号转义为本身

  • 反斜杠下$符号代表美元
#反斜杠下$符号代表美元
pat=r'\$[0-9.]+'
print(looking(pat, 'it costs $99.99'))
#结果
['$99.99']
  • 反斜杠的限制下, ^ . \ 终于代表乘方、小数点和除号
pat = r'(\\|\/|\^|\.)'
print( looking(pat, '(5/2)^2=6.25') )
#结果
['/', '^', '.']

# 没有了反斜杠的限制,一切乱了套
#点 . 就是通配符,可以匹配字符串里所有字符。
pat = r'(\|/|^|.)'
print( looking(pat, '(5/2)^2=6.25') )
# 结果
['', '(', '5', '/', '2', ')', '^', '2', '=', '6', '.', '2', '5']
  • 如果在中括号 [] 集合里,每个字符就是它本身的意义,点就是点,而不是通配符
pat=r'[/^\.]'
print(looking(pat, '(5/2)^2=6.25'))
#结果
['/', '^', '.']

2.7 反斜杠\,本身转义为特殊符号

符号含义
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
\B匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
\d匹配任何“数字”字符,等价于 [0-9]
\D匹配任何“非数字”字符,等价于 [^0-9]
\s匹配任何“空白”字符,等价于 [\t\n\r]
\S匹配任何“非空白”字符,等价于 [^\t\n\r]
\w匹配任何“字母数字下划线”字符,等价于 [a-zA-Z0-9_]
\W匹配任何“非字母数字下划线”字符,等价于 [^a-zA-Z0-9_]
\A匹配句子的“开头”字符,等价于 ^
\Z匹配句子的“结尾”字符,等价于 $
\t匹配句子的“制表键 (tab)”字符
\r匹配句子的“回车键 (return)”字符
\n匹配句子的“换行键 (newline)”字符
?!不包含,表示字符不能出现在目标字符串里,如果要在整个字符串中全部排除某个字符,就加上^和$符号
  • \b \B:匹配一个单词边界,也就是指单词和空格间的位置、匹配非单词边界
pat = r'\blearn\b'
print( looking(pat, 'learn Python') )
print( looking(pat, 'relearn Python') )
print( looking(pat, 'learning Python') )
print( looking(pat, 'relearning Python') )
#输出
['learn']
空
空
空

\b 只能匹配 learn单词的边界,那么只能匹配不带前缀和后缀的 learn, 即 learn 本身。

pat = r'\Blearn\B'
print( looking(pat, 'learn Python') )
print( looking(pat, 'relearn Python') )
print( looking(pat, 'learning Python') )
print( looking(pat, 'relearning Python') )
#输出
空
空
空
['learn']

\B 匹配 learn单词的非边界,那么只能匹配带前缀和后缀的 learn,即 relearning。

pat = r'\blearn\B'
print( looking(pat, 'learn Python') )
print( looking(pat, 'relearn Python') )
print( looking(pat, 'learning Python') )
print( looking(pat, 'relearning Python') )
#输出
空
空
['learn']

learn 前 \b 后 \B,只能匹配带后缀的 learn,即 learning。

pat = r'\Blearn\b'
print( looking(pat, 'learn Python') )
print( looking(pat, 'relearn Python') )
print( looking(pat, 'learning Python') )
print( looking(pat, 'relearning Python') )
#输出['learn']
空
空

learn 前 \B 后 \b,只能匹配带前缀的 learn,即 relearn。

  • \d \D:匹配数字字符、匹配非数字字符
pat = r'\d+'
print( looking(pat, '12+ab34-cd56*ef78/gh90%ij'))
#输出
['12', '34', '56', '78', '90']
pat = r'\D+'
print( look_for(pat, '12+ab34-cd56*ef78/gh90%ij') )
#输出
['+ab', '-cd', '*ef', '/gh', '%ij']
  • \s \S:匹配空白字符、匹配非空白字符
pat = r'\s+'
s = '''please  don't
leave  me
    alone'''
print( looking(pat, s) )
#输出
[' ', '\n', ' ', '\n ']
#匹配各种空格比如制表、回车或新行。
pat = r'\S+'
print( looking(pat, s) )
#输出
['please', "don't", 'leave', 'me', 'alone']
#匹配各种非空格
  • \w \W:匹配任何“字母数字下划线”字符、匹配非数字字母下划线
pat=r'\w+'
print(looking(pat, '12+ab_34-cd56_ef78'))
#结果
['12', 'ab_34', 'cd56_ef78']
pat=r'\W+'
print(looking(pat, '12+ab_34-cd56_ef78'))
#结果
['+', '-']
  • \A \Z:匹配开头、匹配结尾
pat1 = r'^y[\w]*'
pat2 = r'\Ay[\w]*'
str1 = 'you rock'
str2 = 'rock you'

print( look_for(pat1, str1) )
print( look_for(pat2, str1) )

print( look_for(pat1, str2) )
print( look_for(pat2, str2) )
#结果
['you']
['you']
空
空

匹配开头字符,\A 和 ^ 等价。


pat1 = r'[\w]*k$'
pat2 = r'[\w]*k\Z'
str1 = 'you rock'
str2 = 'rock you'

print( look_for(pat1, str1) )
print( look_for(pat2, str1) )

print( look_for(pat1, str2) )
print( look_for(pat2, str2) )
#结果
['rock']
['rock']
空
空

匹配结尾字符,\Z 和 $ 等价。

3、常用函数

函数含义
match(pat, str)检查字符串str的开头是否符合pat模式
search(pat, str)检查字符串中是否符合某个模式
findall(pat, str)返回所有符合某个模式的字符串,以列表形式输出
finditer(pat, str)返回所有符合某个模式的字符串,以迭代器形式输出
split(pat, str)以某个模式为分割点,拆分整个句子为一系列字符串,以列表形式输出
sub(pat, repl, str)句子 str 中找到匹配正则表达式模式的所有子字符串,用另一个字符串 repl 进行替换
compile(pat)将某个模式编译成对象,供之后使用

3.1 match(pat, str)

判断模式是否在字符串开头位置匹配。如果匹配,返回对象,如果不匹配,返回 None。

s = 'Kobe Bryant'
print( re.match(r'Kobe', s) )
print( re.match(r'Kobe', s).group() )
print( re.match(r'Bryant', s) )
#结果
<re.Match object; span=(0, 4), match='Kobe'>
Kobe
None

该函数返回的是个对象(包括匹配的子字符串和在句中的位置索引),如果只需要子字符串,需要用 group() 函数。

由于值匹配句头,那么句中的 Bryant 无法被匹配到。

3.2 search(pat, str)

在字符串中查找匹配正则表达式模式的位置。如果匹配,返回对象,如果不匹配,返回 None。

s = 'Kobe Bryant'
print( re.search(r'Kobe', s) )
print( re.search(r'Kobe', s).group() )
print( re.search(r'Bryant', s) )
print( re.search(r'Bryant', s).group() )
#结果
<re.Match object; span=(0, 4), match='Kobe'>
Kobe
<re.Match object; span=(5, 11), match='Bryant'>
Bryant

该函数返回的是个对象(包括匹配的子字符串和在句中的位置索引),如果只需要子字符串,需要用 group() 函数。
如果句子出现两个 Bryant 呢?

s = 'Kobe Bryant loves Gianna Bryant'
print( re.search(r'Bryant', s) )
print( re.search(r'Bryant', s).group() )
print( re.search(r'Bryant', s) )
#结果
<re.Match object; span=(5, 11), match='Bryant'>
Bryant
<re.Match object; span=(5, 11), match='Bryant'>

根据结果只匹配出第一个,我们需要下面的函数来匹配全部。

3.3 findall(pat, str)

在字符串中找到正则表达式所匹配的所有子串,并组成一个列表返回。

s = 'Kobe Bryant loves Gianna Bryant'
print( re.findall(r'Kobe', s) )
print( re.findall(r'Bryant', s) )
print( re.findall(r'Gigi', s) )
#结果
['Kobe']
['Bryant', 'Bryant']
[]

3.3 finditer(pat, str)

和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并组成一个迭代器返回。

s = 'Kobe Bryant loves Gianna Bryant'
print( [i.group() for i in re.finditer(r'Kobe', s)] )
print( [i for i in re.finditer(r'Bryant', s)] )
print( [i for i in re.finditer(r'Gigi', s)] )
#结果
['Kobe']
[<re.Match object; span=(5, 11), match='Bryant'>,
 <re.Match object; span=(25, 31), match='Bryant'>]
[]

如果需要匹配子串在原句中的位置索引,用 finditer,此外用 findall。

3.4 split(pat, str)

将字符串匹配正则表达式的部拆分开并返回一个列表。

s = 'Kobe Bryant loves Gianna Bryant'
print( re.split(r'\s', s) ) #按空格拆分
#结果
['Kobe', 'Bryant', 'loves', 'Gianna', 'Bryant']

3.5 sub(pat, repl, str)

句子 str 中找到匹配正则表达式模式的所有子字符串,用另一个字符串 repl 进行替换。如果没有找到匹配模式的串,则返回未被修改的句子 str,其中 repl 既可以是字符串也可以是一个函数。

s = 'Kobe Bryant loves Gianna Bryant'
print( re.sub(r'\s', '-', s) ) #用-替代空格
#结果
Kobe-Bryant-loves-Gianna-Bryant
print( re.sub(r'Gianna', 'Gigi', s) )
#结果
Kobe Bryant loves Gigi Bryant
#用 Gigi 代替 Gianna。
print( re.sub(r'\d+', '_', s) )
#结果
Kobe Bryant loves Gianna Bryant
#用 _ 代替数字(一个或多个),但句中没有数字,因此没用替代动作。
print( re.sub(r'\d*', '_', s)
#结果
_K_o_b_e_ _B_r_y_a_n_t_ _l_o_v_e_s_ _G_i_a_n_n_a_ _B_r_y_a_n_t_
#用 _ 代替数字(零个或多个),虽然句中没有数字,但是零个数字就是空字符,因此 _ 替代所有空字符。

3.6 compile(pat)

把正则表达式的模式转化成正则表达式对象,供其他函数如match 和 search 使用。对象创建出来可以循环使用,如果某种模式要重复使用话,用“先 compile 再 findall”的方式更加高效。
用处理电邮地址来举例。

email = '''Shengyuan Personal: quantsteven@gmail.com
Shengyuan Work: shengyuan@octagon-advisors.com
Shengyuan School: g0700508@nus.edu.sg
Obama: barack.obama@whitehouse.gov'''
print(email)

创建电邮的模式 r’[\w.-]+@[\w.-]+',用 compile 先创建 re 对象,供之后使用。

pat = r'[\w.-]+@[\w.-]+'
obj = re.compile(pat)
obj
#结果
re.compile(r'[\w.-]+@[\w.-]+', re.UNICODE)

在对象 obj 上分别使用 match, search, findall, findieter 等方法,结果如下:

print( obj.match(email), '\n') #匹配以obj对象开头
print( obj.search(email), '\n' ) #找到第一个obj
print( obj.findall(email), '\n' ) #找到所有的obj
print( [i for i in obj.finditer(email)]) #找到所有的obj迭代对象
#结果
None

<re.Match object; span=(20, 41), match='quantsteven@gmail.com'> 

['quantsteven@gmail.com',
 'shengyuan@octagon-advisors.com',
 'g0700508@nus.edu.sg',
 'barack.obama@whitehouse.gov']

[<re.Match object; span=(20, 41), match='quantsteven@gmail.com'>,
<re.Match object; span=(58, 88), match='shengyuan@octagon-advisors.com'>,
<re.Match object; span=(107, 126), match='g0700508@nus.edu.sg'>,
<re.Match object; span=(134, 161), match='barack.obama@whitehouse.gov'>]

在对象 obj 上还可使用 sub 方法,结果如下:

print( obj.sub('---@---.---', email), '\n' )
#结果
Shengyuan Personal: ---@---.---
Shengyuan Work: ---@---.---
Shengyuan School: ---@---.---
Obama: ---@---.---

在对象 obj 上还可使用 split 方法,即把 @ 前后的子串拆分出来,结果如下:

for addr in obj.findall(email):
    print( re.split(r'@', addr))
#结果
['quantsteven', 'gmail.com']
['shengyuan', 'octagon-advisors.com']
['g0700508', 'nus.edu.sg']
['barack.obama', 'whitehouse.gov']

我们还可以再创建个 RE 对象 obj1,专门用来做拆分。

obj1 = re.compile(r'@')
for addr in obj.findall(email):
    print( obj1.split(addr))
#结果
['quantsteven', 'gmail.com']
['shengyuan', 'octagon-advisors.com']
['g0700508', 'nus.edu.sg']
['barack.obama', 'whitehouse.gov']

4、 综合实例

4.1 密码例子

密码通常有如下要求:

  • 最少 8 个最多 16 个字符.
  • 至少含有一个大写字母,一个小写字母,一个数字
  • 至少含有一个特殊字符@ ! $ # % _ - ,但不包括空格
pat=r'^[a-z0-9A-Z@!$#%_-]{8,16}$' #必须加^和$
print( look_for(pat, 'stevenliu') )
print( look_for(pat, '19831031') )
print( look_for(pat, 'steven1031') )
print( look_for(pat, 'steven@1031') )
print( look_for(pat, 'Steven@1031') )
print( look_for(pat, 's1031') ) #长度小于8
print( look_for(pat, 's@1031') ) #长度小于8
print( look_for(pat, 'stevenliu19831031') )#长度大于16,如果不加^和$的话,会匹配出stevenliu19831031和stevenliu@19831031,但这2个明显长度大于16了。
print( look_for(pat, 'stevenliu@19831031') )#长度大于16
#结果
['stevenliu']
['19831031']
['steven1031']
['steven@1031']
['Steven@1031']
空
空
空
空

结果好像不太对,因为密码必须要含有数字,大小写和特殊字符。
这时候需要用 (?=…) 这个操作了,意思就是匹配 ’…’ 之前的字符串。在本例中 ‘…’ 包括小写 [a-z],大写 [A-Z],数字 \d,特殊字符 [@$!%*?&_],言下之义就是上面这些必须包含中密码中。
说明

  • 环视:只进行子表达式的匹配,不占有字符,匹配到的内容不保存到最终的匹配结果,是零宽度的。环视匹配的最终结果就是一个位置。
    环视的作用相当于对所在位置加了一个附加条件,只有满足这个条件,环视子表达式才能匹配成功。
    环视按照方向划分有顺序逆序两种,按照是否匹配有肯定否定两种,组合起来就有四种环视。顺序环视相当于在当前位置右侧附加一个条件,而逆序环视相当于在当前位置左侧附加一个条件。
  • (?<=Expression):逆序肯定环视,表示所在位置左侧能够匹配Expression
    (?<!Expression):逆序否定环视,表示所在位置左侧不能匹配Expression
    (?=Expression):顺序肯定环视,表示所在位置右侧能够匹配Expression
    (?!Expression):顺序否定环视,表示所在位置右侧不能匹配Expression

参考:https://www.cnblogs.com/kernel0815/p/3375249.html

pat = r'^(?=.*[a-z])
         (?=.*[A-Z])
         (?=.*\d)
         (?=.*[$@$!%*?&_])
         [A-Za-z\d$@$!%*?&_]{8,16}$'

print( look_for(pat, 'stevenwang') )
print( look_for(pat, '19831031') )
print( look_for(pat, 'steven1031') )
print( look_for(pat, 'steven@1031') )
print( look_for(pat, 'Steven@1031') )
print( look_for(pat, 's1031') )
print( look_for(pat, 's@1031') )
print( look_for(pat, 'stevenwang19831031') )
print( look_for(pat, 'stevenwang@19831031') )
空
空
空
空
['Steven@1031']
空
空
空
空

结果完全正确。

4.2 邮箱例子

首先定义邮箱地址的模式 ‘\S+@\S+’,还记得 \S 是非空格字符,基本代表了所需的字符要求。我们想从从 email.txt 文本中筛选出所有邮箱信息。

pat = r'\S+@\S+'
obj = re.compile(pat)
email_list = []
hand = open('email.txt')
for line in hand:
    line = line.rstrip()
    email_addr = obj.findall(line)
    if len(email_addr) > 0:
        email_list.append(email_addr[0])
list(set(email_list))

在这里插入图片描述
咋一看结果是对的,但细看(高亮处)有些邮箱地址包含了 <> 的符号,或者根本不是正常的邮箱地址,比如 apache@localhost。
这时候我们需要在模式中添加更多规则,如下:

  • '[a-zA-Z\d]\S+ 代表第一字符要是数字或字母
  • \w+\.[a-z]{2,3} 代表 A.B 这样的结构,其中 A 由若干字母数字下划线组成,而 B 由 2 或 3 个小写字母组成(因为通常邮箱最后就是 com, net, gov, edu 等等)。
pat = r'[a-zA-Z\d]\S+@\w+\.[a-z]{2,3}'

在这里插入图片描述
结果完全正确。

4.3 摘要例子

在下面摘要中获取人物、买卖动作、股票数量、股票代号、日期和股价这些关键信息。

news = 
"""
Jack Black sold 15,000 shares in AMZN on 2019-03-06 at a price of $1044.00.
David V.Love bought 811 shares in TLSA on 2020-01-19 at a price of $868.75.
Steven exercised 262 shares in AAPL on 2020-02-04 at a price of $301.00.
"""
pat = r'([a-zA-Z]*)' \
        '\s(sold|bought|exercised)' \
        '\s*([\d,]+)' \
        '.*in\s([A-Z]{,5})' \
        '.*(\d{4}-\d{2}-\d{2})' \
        '.*price of\s(\$\d*.\d*)'

re.findall( pat, news )
#结果
[('Jack Black', 'sold', '15,000', 'AMZN', '2019-03-06', '$1044.00'),
 ('David V.Love', 'bought', '811', 'TLSA', '2020-01-19', '$868.75'),
 ('Steven', 'exercised', '262', 'AAPL', '2020-02-04', '$301.00')]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值