19 | 正则表达式

原文连接:https://www.jianshu.com/p/4c5765044c28
作者:shark

一、正则表达式介绍

正则表达式(称为RE,或正则,或正则表达式模式)指定了一组与之匹配的字符串;本质上是嵌入在Python中的一种微小的、高度专业化的编程语言,可通过 re 模块获得。

模块内的函数可以让你检查某个字符串是否跟给定的正则表达式匹配(或者一个正则表达式是否匹配到一个字符串,这两种说法含义相同)。

使用这种小语言,你可以为要匹配的可能字符串集指定规则;此集可能包含英语句子,电子邮件地址,TeX命令或你喜欢的任何内容。 然后,您可以询问诸如“此字符串是否与模式匹配?" 或者 "模式匹配到了哪些字符串" 。

其实很多其他语言或者软件工具都会支持正则表达式。

比如 Java Perl JS vi sed awk 等。Python 中的 re 模块提供的是类似 Perl 语言中的正则表达式。

正则表达式模式被编译成一系列字节码,然后由用 C 编写的匹配引擎执行。

友情提示:

正则表达式语言相对较小且受限制,因此并非所有可能的字符串处理任务都可以使用正则表达式完成。

还有一些任务 可以 用正则表达式完成,但表达式变得非常复杂。 在这些情况下,你最好编写 Python 代码来进行处理;虽然 Python 代码比精心设计的正则表达式慢,但它也可能更容易理解。

二、正则表达式语法介绍

正则表达式可以拼接; 如果 A 和 B 都是正则表达式, 那么 AB 也是正则表达式。

正则表达式可以包含普通或者特殊字符。绝大部分普通字符,比如 'A', 'a', 或者 '0',都是最简单的正则表达式。它们就匹配自身。

可以拼接普通字符,所以 last 匹配字符串 'last'. (在此文档的其他部分,我们将用 lenovo 这种不带引号的方式表示正则表达式,要匹配的字符串用 'lenovo' ,单引号形式。比如: 正则表达式 lenovo 可以匹配到字符串 'lenovo')。

三、匹配字符

由于正则表达式用于对字符串进行操作,因此我们将从最常见的任务开始:匹配字符

1. 普通字符

大多数字母和字符只会匹配自己。 例如,正则表达式 test 将完全匹配字符串 test

你可以启用一个不区分大小写的模式,让这个正则匹配 TestTEST,稍后会介绍。

2. 特殊字符

一些字符是特殊的 metacharacters (元字符),并且不匹配自己。 相反,它们表示应该匹配一些与众不同的东西,或者通过重复它们或改变它们的含义来影响正则的其他部分。

本文档的大部分内容都致力于讨论各种元字符及其功能。

接下来一个一个介绍,在展示示例的时候,我使用了 re 模块中的 search() 方法。这个方法就是从整个字符串的开头查找符合正则模式的字符,直到找到第一个匹配的字符为止,后面假如还有符合的字符串,也不会继续匹配。

返回的是一个 RE 对象,匹配到的内容会作为对象 match 属性的值。

正则特殊字符 匹配内容
. 匹配除换行符(\n)以外的单个任意字符
In [6]: import re

In [7]: s = '联想 lenovo 2020'

In [8]: re.search('.', s)
Out[8]: <_sre.SRE_Match object; span=(0, 1), match='联'>

正则特殊字符 匹配内容
^ 匹配整个字符串的开头
In [18]: re.search('^联想', s)
Out[18]: <_sre.SRE_Match object; span=(0, 2), match='联想'>

正则特殊字符 匹配内容
$ 匹配整个字符串的结尾
In [20]: re.search('2020$', s)
Out[20]: <_sre.SRE_Match object; span=(10, 14), match='2020'>

正则特殊字符 匹配内容
[] 匹配中括号内字符中的任何一个 比如:[1a] 匹配到 1 或者 a
In [21]: re.search('[en]', s)
Out[21]: <_sre.SRE_Match object; span=(4, 5), match='e'>

正则特殊字符 匹配内容
[-] 匹配连续的字符范围 比如 [0-9] 表示 09之间的任意一个数字
In [22]: re.search('[a-z]', s)
Out[22]: <_sre.SRE_Match object; span=(3, 4), match='l'>

In [23]: re.search('[0-9]', s)
Out[23]: <_sre.SRE_Match object; span=(10, 11), match='2'>

In [24]: re.search('[0-9a-z]', s)
Out[24]: <_sre.SRE_Match object; span=(3, 4), match='l'>

注意,在 中括号内的任何元字符都不生效,都会成为普通的字符,只能匹配到自身。比如:[akm$] 将匹配'a''k''m''$' 中的任意一个字符;'$' 通常是一个元字符,但在一个中括号内它被剥夺了特殊性。

In [28]: s1 = '$520.00'

In [29]: re.search('00$', s1)
Out[29]: <_sre.SRE_Match object; span=(5, 7), match='00'>

In [30]: re.search('[00$]', s1)
Out[30]: <_sre.SRE_Match object; span=(0, 1), match='$'>)

^ 在中括号中是取反的意思,比如 [^a58] 匹配到不是 a58 中的任意一个其他字符 。

In [41]: s2 = 'ab58abc'

In [42]: re.search('[^a58]', s2)
Out[42]: <_sre.SRE_Match object; span=(1, 2), match='b'>

正则特殊字符 匹配内容
\w 匹配单个字母、数字、汉字或下划线 类似于 [a-zA-Z0-9_]
In [37]: re.search('e\w', s)
Out[37]: <_sre.SRE_Match object; span=(4, 6), match='en'>

In [38]: re.search('e\w\w', s)
Out[38]: <_sre.SRE_Match object; span=(4, 7), match='eno'>

正则特殊字符 匹配内容
\W 匹配任何不是单词字符的字符。 这与 \w 正相反。类似于 [!a-zA-Z0-9_]
In [254]: re.search('\W', '.')
Out[254]: <_sre.SRE_Match object; span=(0, 1), match='.'>

In [261]: re.search('\W', ' ')
Out[261]: <_sre.SRE_Match object; span=(0, 1), match=' '>

正则特殊字符 匹配内容
\d 匹配单个数字
In [40]: re.search('\d\d\d', s)
Out[40]: <_sre.SRE_Match object; span=(10, 13), match='202'>

正则特殊字符 匹配内容
\s 匹配单个任意的空白符,这等价于 [ \t\n\r\f\v]
\S 匹配任何非空白字符, [^ \t\n\r\f\v]
In [18]: re.search('^联想', s)
Out[18]: <_sre.SRE_Match object; span=(0, 2), match='联想'>

正则特殊字符 匹配内容
\b 匹配单词边界

注意,通常 \b 定义为 \w\W 之间的边界,或者 \w 和字符串开始/结尾的边界, 意思就是 r'\bfoo\b' 匹配 'foo', 'foo.', '(foo)', 'bar foo baz' 但不匹配 'foobar' 或者 'foo3'

In [44]: s3 = 'hello2020 hello.'

In [45]: re.search('hello', s3)
Out[45]: <_sre.SRE_Match object; span=(0, 5), match='hello'>

In [46]: re.search(r'\bhello\b', s3)
Out[46]: <_sre.SRE_Match object; span=(10, 15), match='hello'>

正则特殊字符 匹配内容
| 表示或的关系,比如 A|BAB 可以是任意正则表达式,创建一个正则表达式,匹配 A 或者 B。循环扫描字符串时,一旦 A 匹配成功, B 就不再进行匹配。如果要匹配 '|' 字符,使用 \|, 或者把它包含在字符集里,比如 [|].
In [48]: s
Out[48]: '联想 lenovo 2020'

In [49]: re.search('e|n', s)
Out[49]: <_sre.SRE_Match object; span=(4, 5), match='e'>

正则特殊字符 匹配内容
(...) (组合),匹配括号内的任意正则表达式,并标识出组合的开始和结尾。匹配完成后,组合的内容可以被获取,并可以在之后用 \number 转义序列进行再次匹配
In [51]: s
Out[51]: '联想 lenovo 2020'

In [52]: re.search('(eno)', s)
Out[52]: <_sre.SRE_Match object; span=(4, 7), match='eno'>

稍后在后面分组时,进行详细说明。


正则特殊字符 匹配内容
. 匹配除换行符(\n)以外的单个任意字符
^ 匹配整个字符串的开头
$ 匹配整个字符串的结尾
[] 匹配中括号内字符中的任何一个 比如:[1a] 匹配到 1 或者 a
[-] 匹配连续的字符范围 比如 [0-9] 表示 09之间的任意一个数字
\w 匹配单个字母、数字、汉字或下划线
\d 匹配单个数字
\s 匹配单个任意的空白符
\b 匹配单词的开始或结束

[0-9a-zA-z] 表示 0 1 2 3 4 5 6 7 8 9 其中任何一个数字,或者 26 个英文字符大小写的任何一个。
\w, \s , \d 可以包含在中括号中。 例如,[\s,.] 是一个匹配任何空格字符的字符类或者 ',' ,或 '.'

重复次数匹配:

正则特殊字符 匹配内容 示例
* 对它前面的正则模式匹配0到任意次重复, 尽量多的匹配字符串。 ab* 会匹配 'a''ab', 或者 'a'后面跟随任意个 'b'
+ 对它前面的正则式匹配1到任意次重复。 lv6+ 匹配 lv 后面跟随1个以上到任意个 '6',它不会匹配 'lv'。
? 对它前面的一个正则模式匹配0到1次重复。 lv6? 匹配 lvlv6
{m} 重复它前一个正则模式 m 次 a{2} 匹配 aa
{m,} 重复它前一个正则模式 m 次或 m 次以上 a{2,} 匹配 aa 或连续两个 a以上
{m, n} 重复前一个正则模式 m 到 n 次之间的任意一个都可以 a{2,5} 匹配连续2 个到 5 个之间的 a。如:'aa', 'aaa', 'aaaa', 'aaaaa'

四、关于原始字符串

正则表达式使用反斜杠('\')来表示特殊形式,或者把特殊字符转义成普通字符。 而反斜杠在普通的 Python 字符串里也有相同的作用,所以就产生了冲突。比如说,要匹配一个字面上的反斜杠,正则表达式模式不得不写成 '\\\\',因为正则表达式里匹配一个反斜杠必须是 '\\' ,而每个反斜杠在普通的 Python 字符串里都要写成 '\\'

解决办法是对于正则表达式样式使用 Python 的原始字符串表示法;在带有 'r' 前缀的字符串字面值中,反斜杠不必做任何特殊处理。 因此 r"\n" 表示包含 '\''n' 两个字符的字符串,而 "\n" 则表示只包含一个换行符的字符串。 样式在 Python 代码中通常都会使用这种原始字符串表示法来表示。

五、python使用正则

1. 常用方法

match

只在整个字符串的起始位置进行匹配

示例字符串

string = "isinstance yangge enumerate www.qfedu.com 1997"

示例演示:

import re
In [4]: r = re.match("is\w+", string)

In [8]: r.group()  # 获取匹配成功的结果
Out[8]: 'isinstance'

search

从整个字符串的开头找到最后,当第一个匹配成功后,就不再继续匹配。

In [9]: r = re.search("a\w+", string)

In [10]: r.group()
Out[10]: 'ance'    

findall

搜索整个字符串,找到所有匹配成功的字符串,比把这些字符串放在一个列表中返回。

In [16]: r = re.findall("a\w+", string)

In [17]: r
Out[17]: ['ance', 'angge', 'ate']

sub

把匹配成功的字符串,进行替换。

# 语法:
"""
("a\w+",    "100",        string,     2)
匹配规则,替换成的新内容,  被搜索的对象, 有相同的话替换的次数


"""
In [24]: r = re.sub("a\w+", "100", string, 2)
  
In [25]: r
Out[25]: 'isinst100 y100 enumerate www.qfedu.com 1997'

# 模式不匹配时,返回原来的值

split

以匹配到的字符进行分割,返回分割后的列表

In [26]: string
Out[26]: 'isinstance yangge enumerate www.qfedu.com  1997'

In [27]: r = re.split("a", string, 1)

使用多个界定符分割字符串

>>> line = 'asdf fjdk; afed, fjek,asdf,  foo'
>>> import re
>>> re.split(r'[;,\s]\s*', line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']

2. 分组

正则分组

==从已经成功匹配的内容中,再去把想要的取出来==

# match
In [64]: string
Out[64]: 'isinstance yangge enumerate www.qfedu.com  1997'

In [65]: r = re.match("is(\w+)", string)

In [66]: r.group()
Out[66]: 'isinstance'

In [67]: r.groups()
Out[67]: ('instance',)
    
    
    
# search
# 命名分组
In [87]: r = re.search("is\w+\s(?P<name>y\w+e)", string)  

In [88]: r.group()
Out[88]: 'isinstance yangge'

In [89]: r.groups()
Out[89]: ('yangge',)

In [90]: r.groupdict()
Out[90]: {'name': 'yangge'}

    
# findall

In [98]: string
Out[98]: 'isinstance all yangge any enumerate www.qfedu.com  1997'

In [99]: r = re.findall("a(?P<name>\w+)", string)

In [100]: r
Out[100]: ['nce', 'll', 'ngge', 'ny', 'te']

In [101]: r = re.findall("a(\w+)", string)

In [102]: r
Out[102]: ['nce', 'll', 'ngge', 'ny', 'te']

    
# split
In [113]: string
Out[113]: 'isinstance all yangge any enumerate www.qfedu.com 1997'

In [114]: r = re.split("(any)", string)

In [115]: r
Out[115]: ['isinstance all yangge ', 'any', ' enumerate www.qfedu.com 1997']

In [116]: r = re.split("(a(ny))", string)

In [117]: r
Out[117]:
['isinstance all yangge ',
 'any',
 'ny',
 ' enumerate www.qfedu.com 1997']

In [118]: tag = 'value="1997/07/01"'

In [119]: s = re.sub(r'(value="\d{4})/(\d{2})/(\d{2})"', r'\1年\2月\3日"', tag)

In  [120]: s
Out [120]: value="1997年07月01日"   

4. 常用正则分享(邮箱号、手机号、IP)

4.1 邮箱账号


shark123@qq.com
shark123@163.com

r'\w+@(163|126|qq|139).(com|cn)'
或者
r'[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+'

4.2 手机号

r'1( 5[02689]|3\d|7[279]|8\d)\d{8}'

4.3 IP 地址

A类IP地址的范围为 1.0.0.1 ----- 126.255.255.254
B类IP地址的范围为128.1.0.1 ---- 191.255.255.254
C类IP地址的范围为 192.0.1.1 ---- 223.255.255.254
D类 IP地址的范围为 224.0.0.1 ---- 239.255.255.254

127.x.x.x 是 本地回环接口地址

分解动作

(?:2[0-3]\d|1\d\d|[1-9]\d{0,1})\.  # 匹配 IP 地址的前 8 个字节
(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})\.){2} # 匹配 IP 地址的 中间 16 个字节
   (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d{0,1})  # 匹配 IP 地址的后 8 个字节

(?:…)
正则括号的非捕获版本。 匹配在括号内的任何正则表达式,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用。

In [213]: re.findall(r'(2[0-3]\d|1\d\d|[1-9]\d{0,1})\.', '239.')
Out[213]: ['239']

In [214]: re.findall(r'(?:2[0-3]\d|1\d\d|[1-9]\d{0,1})\.', '239.')
Out[214]: ['239.']

合并

pattern = r'\b(?:2[0-3]\d|1\d\d|[1-9]\d{0,1})\.(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})\.){2}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})\b'


由于 \b 是匹配
比如:

In [283]: re.findall(r'\bfoo\b', 'foo.foo')
Out[283]: ['foo', 'foo']

In [284]: re.findall(r'foo(?![.])', 'foo.foo')
Out[284]: ['foo']

也可以是这样

r'(?<![.\d])(?:2[0-3]\d|1\d\d|[1-9]\d{0,1})\.(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})\.){2}?(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})(?![.\d])'

(?<!…):
匹配当前位置之前不是 ... 的样式。这个叫 negative lookbehind assertion (后视断定取非)。
(?!…)
匹配 不符合的情况。这个叫 negative lookahead assertion (前视取反)。比如说, Isaac (?!Asimov) 只有后面 不 是 'Asimov' 的时候才匹配 'Isaac '

示例:

s = 'lenovo 192.137.1.336 shark 192.168.1.130 255.255.255.255 qf 192.168.1.138 lenovo'

贪婪 和 非贪婪

# 匹配所有包含小数在内的数字
print(re.findall('\d+\.?\d*',"asdfasdf123as1.13dfa12adsf1asdf3")) #['123', '1.13', '12', '1', '3']

#.*默认为贪婪匹配
print(re.findall('a.*b','a1b22222222b')) #['a1b22222222b']

#.*?为非贪婪匹配:推荐使用
print(re.findall('a.*?b','a1b22222222b')) #['a1b']
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270