python正则表达式很难?一文带你轻松掌握

python正则表达式很难?一文带你轻松掌握

 

本文含 10026 字 , 27 图表截屏python

建议阅读 42 分钟正则表达式

引言apache

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

最简单的 RE 例子以下。curl

python正则表达式很难?一文带你轻松掌握

 

 

'steven'

很明显,这样一个 RE 只能搜索出含 steven 的字符串。函数

你能够用 Python 代码来验证,但如今假设咱们还不会写,咱们能够去 https://regex101.com/来验证。以下图右上角所示,匹配成功。网站

python正则表达式很难?一文带你轻松掌握

 

这样来搜索未免太傻了,有没有稍微智能一点的方法。再看下面的 RE。url

^s....n$

上面的 RE 定义的模式以下: 任何 6 个字符的单词,以 s 开头 (^s 的效果),以 n 收尾 (n$ 的效果)。 之因此是 6 个字符,是由于有 4 个点 (.) 加上 s 和 n 字符。用上面那个网站作验证,这个 RE ^s....n$ 的若干匹配结果以下:spa

  • seven:不匹配(五个字母)
  • strong man:不匹配(十个字母加空格)
  • soften:匹配
  • steven:匹配
  • Steven:不匹配

看最后两个 steven 和 Steven,区别是第一个字母的大小写,若是我想匹配二者怎么办呢?用下面的 REdebug

^[s|S]....n$

中括号 [] 表示一个集合,而 | 分隔集合里面的元素,在本例是 s 和 S。意思就是匹配开头的 s 或 S,结尾是 n 的 6 字符的单词。

python正则表达式很难?一文带你轻松掌握

 

python正则表达式很难?一文带你轻松掌握

 

这样每次固定单词长度也不太智能吧(好比长度为 n 就须要手动输入 n 个点 .),开头 s 结尾 n 的单词好多呢,我若是都想搜索出来该怎么办呢?看下面的 RE

^s[a-z]+n$

如今 sun 和 strengthen 均可以匹配出来了。起做用的是 [a-z]+,[a-z] 表示小写的字母 a 到 z 的集合,而 + 表明大于一次,联合在一块儿的意思就是该单词“以 s 开头,以 n 结尾,中间有大于一个的任何小写字母”。

python正则表达式很难?一文带你轻松掌握

 

python正则表达式很难?一文带你轻松掌握

 

但上述模式对单词 self-restrain 不起做用,由于有个短链接线(hyphen)。

python正则表达式很难?一文带你轻松掌握

 

不要紧,咱们把 - 加入字母集合里,写成 [a-z-]+,注意第一个 - 表示从 a 到 z,第二个 - 表示短链接线。如今能够匹配 self-restrain 了。

python正则表达式很难?一文带你轻松掌握

 

目前对 RE 有点感受了吧,即使不会确切的表示也不要紧,由于这就是本帖要介绍的。仍是那句话,兴趣最重要,有兴趣才能有效的往下看。

本帖结构以下:

  1. 原始字符串
  2. 五类元字符
  3. 七个函数
  4. 三个实例

注:本帖里的 RE 可视化可参考连接 https://www.debuggex.com/ 。

1

原始字符串

原始字符串(raw string)是全部的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符,一般简称为 r-string。

若是没有转义字符,原始字符串和普通字符串是同样的,好比

print('hello')print(r'hello')
hello
hello

若是有转义字符(用反斜线 \),原始字符串和普通字符串是不同的,好比

print('\blake')print(r'\blake')
lake
\blake

所以,无论何时用原始字符串准没错。

2

元字符

元字符(meta character)就是一种自带特殊含义的字符,也叫特殊字符。好比 [] * + ? {} | () . ^ $ \ ,原字符按用途可分五类:

  • 表示集合: []
  • 表示次数: * + ? {}
  • 表示并列: |
  • 用于提取: ()
  • 用于转义: . ^ $ \

首先定义一个函数,当在句子(是个字符串 str)没有发现模式 pat 时,返回“没有找到”,反之打印出全部符合模式的子字符串。

import redef look_for(pat, str):    return '没有找到' if re.search(pat, str) is None                       else re.findall(pat, str)

上面代码中的 re 是 Python 中正则表达式的库,而 search 和 findall 是包里的两个函数,顾名思义它们作的就是 搜索 和 找出所有 的意思,第三节会详解讲。

2.1

集合字符

中括号(square bracket)- []

中括号表示一个字符集,即建立的模式匹配中括号里指定字符集中的任意一个字符,字符集有三种方式来表现:

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

下面咱们来一一细看。

明确字符

匹配中括号里面的任意一个字符。

pat = r'[abc]'
print( look_for(pat, 'a') )print( look_for(pat, 'ac') )print( look_for(pat, 'cba') )print( look_for(pat, 'steven') )
['a']
['a', 'c']
['c', 'b', 'a']
没有找到

分析以下:

该模式只匹配字符 a,b 或者 c,所以前三个例子的字符串里都有相应字符匹配,而最后例子里的 steven 不包含 a, b 或 c。

模式 [abc] 的可视图以下,注意 “One of” 是说集合里面的字符是“或”的关系。

python正则表达式很难?一文带你轻松掌握

 

范围字符

在 [ ] 中加入 - 便可设定范围,好比

  • [a-e] = [abcde]
  • [1-4] = [1234]
  • [a-ep] = [abcdep]
  • [0-38] = [01238]

看两个例子。

print( look_for(r'[a-ep]', 'person') )print( look_for(r'[0-38]', '666') )
['p', 'e']
没有找到

分析以下:

  • 例一的模式等价于 [abcdep],匹配单词 person 里面的 p 和 e 字符。
  • 例二的模式等价于 [01238],不匹配单词 666 里面的任何字符。

模式 [a-ep] 和 [0-38] 的可视图以下。

python正则表达式很难?一文带你轻松掌握

 

python正则表达式很难?一文带你轻松掌握

 

补集字符

在 [ ] 中加入 ^ 便可除去后面的字符集,好比

  • [^abc] 就是非 a, b, c 的字符
  • [ ^123] 就是非 1, 2, 3 的字符

看四个例子。

print( look_for(r'[^abc]', 'baba') )print( look_for(r'[^abc]', 'steven') )print( look_for(r'[^123]', '456') )print( look_for(r'[^123]', '1+2=3') )
没有找到
['s', 't', 'e', 'v', 'e', 'n']
['4', '5', '6']
['+', '=']

分析以下:

  • 例一 baba 里面全部字母不是 a 就是 b,所以没有匹配
  • 例二 steven 全部字母都不是 a, b 和 c,所以所有匹配
  • 例三 456 全部字母不是 1,2 和 3,所以所有匹配
  • 例四 1+2=3 有 +号 和 =号 不是 1, 2 和 3,所以它俩匹配

模式 [^abc] 和 [^123] 的可视图以下。注意 “None of” 是说集合里面的字符是的补集。

python正则表达式很难?一文带你轻松掌握

 

python正则表达式很难?一文带你轻松掌握

 

2.2

次数字符

上面的模式有个致命短板,就是只能匹配 一个 字符!这在实际应用几乎没用,所以咱们须要某些带有“次数功能”的元字符,以下:

  • 贪婪模式:
    • * 表示后面可跟 0 个或多个字符
    • + 表示后面可跟 1 个或多个字符
    • ? 表示后面可跟 0 个或 1 个字符
  • 非贪婪模式:
    • *? 表示后面可跟 0 个或多个字符,但只取第一个
    • +? 表示后面可跟 1 个或多个字符,但只取第一个
    • ?? 表示后面可跟 0 个或 1 个字符,但只取第一个

贪婪模式和非贪婪模式的区别在下面讲 ? 的时候会介绍。

星号(asterisk)- *

首先定义“ 字符 u 能够出现 0 次或屡次 ”的模式,结果不须要解释。

pat = r'colou*r'
print( look_for(pat, 'color') )print( look_for(pat, 'colour') )print( look_for(pat, 'colouuuuuur') )
['color']
['colour']
['colouuuuuur']

模式 colou*r 的可视图以下。

python正则表达式很难?一文带你轻松掌握

 

注意 u 附近有三条通路

  1. 上路 跳过 u,表明 零个 u
  2. 中路 穿越 u,表明 一个 u
  3. 下路 循环 u,表明 多个 u

加号(plus sign)- +

首先定义“ 字符 u 能够出现 1 次或屡次 ”的模式,结果不须要解释。

pat = r'colou+r'
print( look_for(pat, 'color') )print( look_for(pat, 'colour') )print( look_for(pat, 'colouuuuuur') )
没有找到
['colour']
['colouuuuuur']

模式 colou+r 的可视图以下。

python正则表达式很难?一文带你轻松掌握

 

注意 u 附近有两条通路

  1. 中路 穿越 u,表明 一个 u
  2. 下路 循环 u,表明 多个 u

问号(question mark)- ?

首先定义“ 字符 u 能够出现 0 次或 1 次 ”的模式,结果不须要解释。

pat = r'colou?r'
print( look_for(pat, 'color') )print( look_for(pat, 'colour') )print( look_for(pat, 'colouuuuuur') )
['color']
['colour']
没有找到

模式 colou+r 的可视图以下。

python正则表达式很难?一文带你轻松掌握

 

注意 u 附近有两条通路

  1. 上路 跳过 u,表明 零个 u
  2. 中路 穿越 u,表明 一个 u

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

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

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

若是模式是 <.+> ,那么咱们要获取的就是 以 < 开头,以 > 结尾,中间有 1 个或多个字符的字符串。这里咱们先提早介绍 . 字符,它是一个通配符,能够表明任何除新行 (\n) 的字符。

pat = r'<.+>'print( look_for(pat, heading) )
['<h1>TITLE</h1>']

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

缘由就是上面用了 贪婪模式 ,即在整个表达式匹配成功的前提下, 尽量多 的匹配。那么其对立的 非贪婪模式 ,就是在整个表达式匹配成功的前提下, 尽量少 的匹配。

实现非贪婪模式只需在最后加一个 ? 字符,代码以下:

pat =  r'<.+?>'print( look_for(pat, heading) )
['<h1>', '</h1>']

结果无需解释。

有意思的是,模式 <.+> 和 <.+?> 的可视化图长得同样,以下。这样咱们就没法从图上分辨是否使用贪婪或非贪婪的模式了,只能从代码中识别了。

python正则表达式很难?一文带你轻松掌握

 

大括号(curly bracket)- {}

有的时候咱们很是明确要匹配的字符出现几回,好比

  • 中国的手机号位数是 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( look_for(r'[a-z]{1}', s) )print( look_for(r'[0-9]{2,}', s) )print( look_for(r'[a-z]{,5}', s) )print( look_for(r'[0-9]{2,4}', s) )
['a', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'c']
['11', '2222']
['a', '', '', 'bbb', '', '', '', '', 'ccccc', '']
['11', '2222']

须要解释的是例三,匹配五个如下的 a 到 z 小写字母,固然也包括零个,所以结果包含那些空字符。

模式 {n} , { ,n} , {n, } 和 {n, m} 的可视图以下:

python正则表达式很难?一文带你轻松掌握

 

上面都是贪婪模式,固然也有其对应的非贪婪模式,但只有 {n, m}? 有意义,缘由本身想。上面的模式 对于前一个字符重复 m 到 n 次,而且取尽量少的状况 。好比在字符串'sssss'中, s{2,4} 会匹配 4 个 s,但 s{2,4}? 只匹配 2 个 s。

2.3

并列字符

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

垂线(vertical line)- |

首先定义“句子出现 like 或 love 一词”的模式。

pat = r'like|love'
print( look_for(pat, 'like you') )print( look_for(pat, 'love you') )
['like']
['love']

模式 like|love 的可视图以下,其并列模式体如今黑点到白点的并行通路上。

python正则表达式很难?一文带你轻松掌握

 

2.4

提取字符

若是你想把匹配的内容提取出来,用小括号,而在小括号里面你能够设计任意正则表达式。

小括号(square bracket)- ()

首先定义“beat 的第三人称,过去式,过去分词和如今进行式”的模式,为了获取 beat 加正确后缀的全部单词。

pat = r'beat(s|ed|en|ing)'
print( look_for(pat, 'beats') )print( look_for(pat, 'beated') )print( look_for(pat, 'beaten') )print( look_for(pat, 'beating') )
['s']
['ed']
['en']
['ing']

咱们将出如今 () 里面的后缀都获取出来了,其可视图以下,咱们发现“Group 1”表明 () 起的做用。

python正则表达式很难?一文带你轻松掌握

 

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

pat = r'(beat(s|ed|en|ing))'
print( look_for(pat, 'beats') )print( look_for(pat, 'beated') )print( look_for(pat, 'beaten') )print( look_for(pat, 'beating') )
[('beats', 's')]
[('beated', 'ed')]
[('beaten', 'en')]
[('beating', 'ing')]

其可视图以下,咱们发现 Group 2 嵌套在 Group 1 里面。

python正则表达式很难?一文带你轻松掌握

 

如今 带着后缀的 beat 已经获取出来了,上面列表中每一个元组的第一个元素,但完美主义者不想要后缀(即元组的第二个元素),能够用下面的骚模式。

在 () 中最前面加入 ?: 。 (?:) 表明只匹配不获取(non-capturing),结果看上去很是天然。

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

其可视图以下,咱们发现只有一个 Group 1,那个内括号,表明不被获取的内容,没有体如今下图中。

python正则表达式很难?一文带你轻松掌握

 

2.5

转义字符

字符 集合 问题解决了,字符 次数 问题解决了,字符 并列 问题解决了,字符 获取 问题解决了,看上去咱们能作不少事了。别急,RE 给你最后一击, 转义字符 ,让模式更增强大。

转义字符,顾名思义,就是能转换自身含义的字符。

点 . 再也不是点,美圆 $ 再也不是美圆,等等等等。。。

点(dot)- .

点 . 表示除新行(newline)的任意字符,它是个通配符。用它最无脑简便,可是代码也最难读懂,效率也最低下。

定义“ 含有 1 个或多个非新行字符 ”的模式。

pat = r'.+'
print( look_for(pat, 'a') )print( look_for(pat, 'b1') )print( look_for(pat, 'C@9') )print( look_for(pat, '$ 9_fZ') )print( look_for(pat, '9z_\t\r\n') )
['a']
['b1']
['C@9']
['$ 9_fZ']
['9z_\t\r']

除了最后例子中的 \n 没有匹配到,其余的字符所有匹配出来。

托字符(carat)- ^

托字符 ^ 表示字符串开头。

定义“ 以 s 开头字符串 ”的模式。

pat = r'^s[\w]*'
print( look_for(pat, 'son') )print( look_for(pat, 'shot') )print( look_for(pat, 'come') )
['son']
['shot']
没有找到

结果太明显,不解释。

美圆符(dollar sign)- $

美圆符 $ 表示字符串结尾。

定义“ 以 s 结尾字符串 ”的模式。

pat = r'[\w]*s$'
print( look_for(pat, 'yes') )print( look_for(pat, 'mess') )print( look_for(pat, 'come') )
['yes']
['mess']
没有找到

结果太明显,不解释。

反斜杠(backslash)- \

更厉害的是,反斜杠 \ 可对特殊字符进行转义,也可对普通字符转义。

  • 将特殊字符转成自身含义:用 \ 做用在 ^ . \ 等身上,表明乘方 \^ 、小数点 \. 和除号 \\
  • 将自身字符转成特殊含义:用 \ 做用在 w d n 等身上,表明字母 \w 、数字 \d 和新行 \n

特殊 --> 自身

在反斜杠的限制下, $ 终于表明美圆了!

pat = r'\$[0-9.]+'
print( look_for(pat, 'it costs $99.99') )
['$99.99']

在反斜杠的限制下, ^ . \ 终于表明乘方、小数点和除号了!

pat = r'(\\|\/|\^|\.)'
print( look_for(pat, '(5/2)^2=6.25') )
['/', '^', '.']

没有了反斜杠的限制,一切乱了套,点 . 就是通配符,能够匹配字符串里全部字符。

pat = r'(\|/|^|.)'
print( look_for(pat, '(5/2)^2=6.25') )
['', '(', '5', '/', '2', ')', '^', '2', '=', '6', '.', '2', '5']

但若是在中括号 [] 集合里,每一个字符就是它自己的意义,点就是点,而不是通配符。

pat = r'[/^\.]'
print( look_for(pat, '(5/2)^2=6.25') )
['/', '^', '.']

自身 --> 特殊

规则总结以下(大写和小写互补,二者加一块儿是全集):

  • \b :匹配空字符串,但仅适用于单词的“首尾”
  • \B :匹配空字符串,但仅适用于单词的“非首尾”
  • \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( look_for(pat, 'learn Python') )print( look_for(pat, 'relearn Python') )print( look_for(pat, 'learning Python') )print( look_for(pat, 'relearning Python') )
['learn']
没有找到
没有找到
没有找到

\b 只能抓住 learn 先后的 首尾 空字符,那么只能匹配 不带前缀和后缀 的 learn, 即 learn 自己。

pat = r'\Blearn\B'print( look_for(pat, 'learn Python') )print( look_for(pat, 'relearn Python') )print( look_for(pat, 'learning Python') )print( look_for(pat, 'relearning Python') )
没有找到
没有找到
没有找到
['learn']

\B 只能抓住 learn 先后的 非首尾 空字符,那么只能匹配 带前缀和后缀 的 learn,即 relearning。

pat = r'\blearn\B'print( look_for(pat, 'learn Python') )print( look_for(pat, 'relearn Python') )print( look_for(pat, 'learning Python') )print( look_for(pat, 'relearning Python') )
没有找到
没有找到
['learn']
没有找到

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

pat = r'\Blearn\b'print( look_for(pat, 'learn Python') )print( look_for(pat, 'relearn Python') )print( look_for(pat, 'learning Python') )print( look_for(pat, 'relearning Python') )
没有找到
['learn']
没有找到
没有找到

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

\d \D

pat = r'\d+'print( look_for(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'tleave  me    alone'''print( look_for(pat, s) )
[' ', '\n', ' ', '\n ']

匹配各类空格好比制表、回车或新行,不解释。

pat = r'\S+'print( look_for(pat, s) )
['please', "don't", 'leave', 'me', 'alone']

匹配各类非空格,不解释。

\w \W

pat = r'\w+'print( look_for(pat, '12+ab_34-cd56_ef78') )
['12', 'ab_34', 'cd56_ef78']

匹配字母数字下划线,不解释。

pat = r'\W+'print( look_for(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 和 $ 等价,不解释。

2

经常使用函数

了解完上节介绍的元字符的基本知识,本节的函数运用就很简单了。RE 包里常见的函数总结以下:

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

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 没法被匹配到。

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'>

根据结果只匹配出第一个,咱们须要下面的函数来匹配所有。

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']
[]

结果不解释。

finditer(pat, str)

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

s = 'Kobe Bryant loves Gianna Bryant'print( [i 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)] )
[<re.Match object; span=(0, 4), match='Kobe'>]
[<re.Match object; span=(5, 11), match='Bryant'>,
 <re.Match object; span=(25, 31), match='Bryant'>]
[]

若是须要匹配子串在原句中的位置索引,用 finditer ,此外用 findall 。

split(pat, str)

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

s = 'Kobe Bryant loves Gianna Bryant'print( re.split(r'\s', s) )
['Kobe', 'Bryant', 'loves', 'Gianna', 'Bryant']

按空格拆分,不解释。

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_

用 _ 代替数字( 零 个或多个 ),虽然句中没有数字,可是零个数字就是空字符,所以 _ 替代全部空字符。好玩吧 :)

compile(pat)

把正则表达式的模式转化成正则表达式 对象 ,供其余函数如 match 和 search 使用。对象建立出来能够循环使用,若是某种模式要重复使用话,用“先 compile 再 findall”的方式更加高效。

用处理电邮地址来举例。

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

建立电邮的模式 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')print( obj.search(email), '\n' )print( obj.findall(email), '\n' )print( [i for i in obj.finditer(email)])
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']

3

示例展现

3.1

密码例子

密码一般有以下要求。

  • 最少 8 个最多 16 个字符.
  • 至少含有一个大写字母,一个小写字母,一个数字
  • 至少含有一个特殊字符 @ $ ! % * ? & _,但不包括空格

根据上面要求建立模式,相信均可以读懂了吧。

pat = r'^[0-9a-zA-Z@!$#%_-]{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') )
['stevenwang']
['19831031']
['steven1031']
['steven@1031']
['Steven@1031']
没有找到
没有找到
没有找到
没有找到

结果好像不太对,由于密码必需要含有数字,大小写和特殊字符。

这时候须要用 (?=...) 这个骚操做,意思 就是匹配 ’…’ 以前的字符串 。在本例中 '...' 包括小写 [a-z],大写 [A-Z],数字 \d,特殊字符 [@$!%*?&_],言下之义就是上面这些必须包含中密码中。

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']
没有找到
没有找到
没有找到
没有找到

结果彻底正确。

上面模式的可视图以下:

python正则表达式很难?一文带你轻松掌握

 

3.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))

python正则表达式很难?一文带你轻松掌握

 

咋一看结果是对的,但细看(高亮处)有些邮箱地址包含了 <> 的符号,或者根本不是正常的邮箱地址,好比 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}'

python正则表达式很难?一文带你轻松掌握

 

结果正常。

3.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')]

上面模式的可视图以下:

python正则表达式很难?一文带你轻松掌握

 

4

总结

累死了,此次真不想总结了。

记住元字符的用处:集合、次数、并列、获取和转义。

记住函数的用法:先 compile 成 RE 对象,在 findall 或 finditer,由你想查看的信息完整度决定。