Re 正则表达式

元字符

注意匹配时要匹配原始字符串,避免发生冲突 用 r”

. ^ $ * + ? {} [] () \ |

.匹配除换行符以外的任意字符

\w匹配字母或数字或下划线或汉字

\s匹配任意的空白符

\d匹配数字

\b匹配单词的开始或结束

^匹配字符串的开始,如果设置了MULTILINE标志,就会变成匹配每一行开始的位置

$匹配字符串的结束

[abc]匹配a或者d或者c[0-9]匹配0到9的数字,相当于\d

*重复零次或更多次

+重复一次或更多次

?重复零次或一次

{n}重复n次

{n,}重复n次或更多次

{n,m}重复n到m次

\W匹配任意不是字母,数字,下划线,汉字的字符

\S匹配任意不是空白符的字符

\D匹配任意非数字的字符

\B匹配不是单词开头或结束的位置

[^x]匹配除了x以外的任意字符

[^aeiou]匹配除了aeiou这几个字母以外的任意字符

一个IP

((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

实现方法

方法 功能

match()判断一个正则表达式是否从开始处匹配一个字符串

search()遍历字符串,找到正则表达式匹配的第一个位置

findall()遍历字符串,找到正则表达式匹配的所有位置,并以列表的形式返回

finditer()遍历字符串,找到正则表达式匹配的所有位置,并以迭代器的形式返回

group()返回匹配的字符串

start()返回匹配的开始位置

end()返回匹配的结束位置

span()返回一个元组表示匹配位置(开始,结束)

编译方式:

charref = re.compile(r”“” 

&[#] # 开始数字引用 

0[0-7]+ # 八进制格式 

| [0-9]+ # 十进制格式 

| x[0-9a-fA-F]+ # 十六进制格式 

; # 结尾分号 

“”“, re.VERBOSE)

分组

在正则表达式中,使用元字符 ( ) 来划分组。 

使用 ( ) 表示的子组我们还可以对它进行按层次索引,可以将索引值作为参数传递给这些方法:group(),start(),end() 和 span()。序号 0 表示第一个分组(这个是默认分组,一直存在的,所以不传入参数相当于默认值 0): 

eg 

>>>p = re.compile(r’(ab)cd’) 

>>>m = p.match(‘abcde’) 

>>>m.group() 

>>>’abcd’ 

>>>m.group(1) 

>>>’ab’ 

子组的索引值是从左到右进行编号,子组也允许嵌套,因此我们可以通过从左往右来统计左括号 ( 来确定子组的序号。 

1.>>> p = re.compile(‘(a(b)c)d’) 

2.>>> m = p.match(‘abcd’) 

3.>>> m.group(0) 

4.’abcd’ 

5.>>> m.group(1) 

6.’abc’ 

7.>>> m.group(2) 

8.’b’ 

group() 方法可以一次传入多个子组的序号: 

1.>>> m.group(2,1,2) 

2.(‘b’, ‘abc’, ‘b’) 

我们还可以通过 groups() 方法一次性返回所有的子组匹配的字符串: 

1.>>> m.groups() 

2.(‘abc’, ‘b’) 

反向引用: 

反向引用指的是你可以在后面的位置使用先前匹配过的内容,用法是反斜杠加上数字。例如 \1 表示引用前边成功匹配的序号为 1 的子组。 

1.>>> p = re.compile(r’(\b\w+)\s+\1’) 

2.>>> p.search(‘Paris in the the spring’).group() 

3.’the the’

扩展语法:(?…))

捕获(exp)匹配exp,并捕获文本到自动命名的组里

(?<name>exp)匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp)

(?:exp)匹配exp,不捕获匹配的文本,也不给此分组分配组号

零宽断言(?=exp)匹配exp前面的位置

(?<=exp)匹配exp后面的位置

(?!exp)匹配后面跟的不是exp的位置

(?< !exp)匹配前面不是exp的位置

注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 

非捕获组: 

有时候你知识需要用一个组来表示部分正则表达式,你并不需要这个组去匹配任何东西,这时你可以通过非捕获组来明确表示你的意图。非捕获组的语法是 (?:…),这个 … 你可以替换为任何正则表达式。 

eg: 

/>>> p = re.compile(r’(?:ab)(c)d’) 

/>>> m = p.match(‘abcde’) 

/>>> m.groups() 

(‘c’,) 

命名组: 

我们再来看另外一个重要功能:命名组。普通子组我们使用序列来访问它们,命名组则可以使用一个有意义的名字来进行访问。

命名组的语法是 Python 特有的扩展语法:(?P)。很明显,< > 里边的 name 就是命名组的名字啦。命名组除了有一个名字标识之外,跟其他捕获组是一样的。

匹配对象的所有方法不仅可以处理那些由数字引用的捕获组,还可以处理通过字符串引用的命名组。除了使用名字访问,命名组仍然可以使用数字序号进行访问: 

1.>>> p = re.compile(r’(?P\b\w+\b)’) 

2.>>> m = p.search( ‘(((( Lots of punctuation )))’ ) 

3.>>> m.group(‘word’) 

4.’Lots’ 

5.>>> m.group(1) 

6.’Lots’

正则表达式中,反向引用的语法像 (…)\1 是使用序号的方式来访问子组;在命名组里,显然也是有对应的变体:使用名字来代替序号。其扩展语法是 (?P=name),含义是该 name 指向的组需要在当前位置再次引用。那么搜索两个单词的正则表达式可以写成 (\b\w+)\s+\1,也可以写成 (?P\b\w+)\s+(?P=word): 

1.>>> p = re.compile(r’(?P\b\w+)\s+(?P=word)’) 

2.>>> p.search(‘Paris in the the spring’).group() 

3.’the the’ 

前向断言

另一个零宽断言,前向断言,前向断言可以分为前向肯定断言和前向否定断言两种形式。

(?=…) 

前向肯定断言。如果当前包含的正则表达式(这里以 … 表示)在当前位置成功匹配,则代表成功,否则失败。一旦该部分正则表达式被匹配引擎尝试过,就不会继续进行匹配了;剩下的模式在此断言开始的地方继续尝试。

(?!…) 

前向否定断言。这跟前向肯定断言相反(不匹配则表示成功,匹配表示失败) 

一个简单的正则表达式模式,这个模式的作用是匹配一个文件名。文件名是用 . 将名字和扩展名分隔开的。例如在 shi.txt 中,fishc 是文件的名字,.txt 是扩展名。

这个正则表达式其实挺简单的:

.*[.].*$

注意,这里用于分隔的 . 是一个元字符,所以我们使用 [.] 剥夺了它的特殊功能。还有 ,我们使用 确保字符串剩余的部分都包含在扩展名中。所以这个正则表达式可以匹配 shi.txt,foio.bar,auc.bat,osoenl.cf,print.conf 等。

现在我们来考虑一种复杂一点的情况,如果你想匹配扩展名不是 bat 的文件,你的正则表达式应该怎么写呢? 

有可能写错的尝试: 

.*[.][^b].*$

为了排除 bat,先排除扩展名的第一个字符为非 b。但这是错误的,因为 foio.bar 后缀名的第一个字符也是 b。

弥补刚刚的错误:

.*[.]([^b]..|.[^a].|..[^t])$

这样第一个字符不是 b,第二个字符不是 a,第三个字符不是 t……这样正好可以接受 foo.bar,排除 autoexec.bat。但问题又来了,这样的正则表达式要求扩展名必须是三个字符,比如 sel.cf 就会被排除掉。

我们接着修复问题:

.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$

在第三次尝试中,让第二个和第三个字符变成可选的。这样就可以匹配稍短的扩展名,比如 sel.cf。更惨的是如果需求改变了,例如你想同时排除 bat 和 exe 扩展名,这个正则表达式模式就变得更加复杂了……

其实,一个前向否定断言就可以解决:

.*[.](?!bat$).*$

解释一下这个前向否定断言的含义:如果正则表达式 bat 在当前位置不匹配,尝试剩下的部分正则表达式;如果 bat 匹配成功,整个正则表达式将会失败(因为是前向否定断言)。(?!bat$) 末尾的 $ 是为了确保可以正常匹配像 sam.bat7h 这种以 bat 开始的扩展名。

同样,有了前向否定断言,要同时排除 bat 和 exe 扩展名,也变得相当容易:

.*[.](?!bat$|exe$).*$

修改字符串

split()在正则表达式匹配的地方进行分割,并返回一个列表

sub()找到所有匹配的子字符串,并替换为新的内容

subn()跟 sub() 一样,但返回新的字符串以及替换的数目

eg: 

正则表达式的 split() 方法将字符串在匹配的地方进行分割,并将分割后的结果作为列表返回。它的做法其实很像字符串的 split() 方法,但这个可以使用更加广泛的分隔符。它同时提供了一个模块级别的函数:re.split()

.split(string[, maxsplit=0]) 

通过正则表达式匹配来分割字符串。如果在 RE 中,你使用了捕获组,那么它们的内容会作为一个列表返回。你可以通过传入一个 maxsplit 参数来设置分割的数量。如果 maxsplit 的值是非 0,表示至多有 maxsplit 个分割会被处理,剩下的内容作为列表的最后一个元素返回。

下边例子中,分隔符是任何非字母数字字符: 

1.>>> p = re.compile(r’\W+’) 

2.>>> p.split(‘This is a test, short and sweet, of split().’) 

3.[‘This’, ‘is’, ‘a’, ‘test’, ‘short’, ‘and’, ‘sweet’, ‘of’, ‘split’, ”] 

4.>>> p.split(‘This is a test, short and sweet, of split().’, 3) 

5.[‘This’, ‘is’, ‘a’, ‘test, short and sweet, of split().’]

如果使用了捕获组,那么作为分隔符的值也会被返回 

1.>>> p = re.compile(r’\W+’) 

2.>>> p2 = re.compile(r’(\W+)’) 

3.>>> p.split(‘This… is a test.’) 

4.[‘This’, ‘is’, ‘a’, ‘test’, ”] 

5.>>> p2.split(‘This… is a test.’) 

6.[‘This’, ‘… ‘, ‘is’, ’ ‘, ‘a’, ’ ‘, ‘test’, ‘.’, ”]

模块级别的函数 re.split() 除了将 RE 作为第一个参数外,其他参数是一样的: 

1.>>> re.split(‘[\W]+’, ‘Words, words, words.’) 

2.[‘Words’, ‘words’, ‘words’, ”] 

3.>>> re.split(‘([\W]+)’, ‘Words, words, words.’) 

4.[‘Words’, ‘, ‘, ‘words’, ‘, ‘, ‘words’, ‘.’, ”] 

5.>>> re.split(‘[\W]+’, ‘Words, words, words.’, 1) 

6.[‘Words’, ‘words, words.’] 

搜索和替换: 

另一个常见的任务就是找到所有的匹配部分,并替换成不同的字符串。sub 方法可以实现!sub 方法有一个 replacement 参数,它可以是一个待替换的字符串,或者一个处理字符串的函数。

.sub(replacement, string[, count=0]) 

返回一个字符串,这个字符串从最左边开始,所有 RE 匹配的地方都替换成 replacement。如果没有找到任何匹配,那么返回原字符串。 

可选参数 count 指定最多替换的次数,必须是一个非负值。默认值是 0,意思是替换所有找到的匹配。

下边是使用 sub() 方法的例子,它会将所有的颜色替换成 color: 

1.>>> p = re.compile( ‘(blue|white|red)’) 

2.>>> p.sub( ‘colour’, ‘blue socks and red shoes’) 

3.’colour socks and colour shoes’ 

4.>>> p.sub( ‘colour’, ‘blue socks and red shoes’, count=1) 

5.’colour socks and red shoes’

subn() 方法跟 sub() 方法干同样的勾当,但区别是返回值为一个包含有两个元素的元组:一个是替换后的字符串,一个是替换的数目。 

1.>>> p = re.compile( ‘(blue|white|red)’) 

2.>>> p.subn( ‘colour’, ‘blue socks and red shoes’) 

3.(‘colour socks and colour shoes’, 2) 

4.>>> p.subn( ‘colour’, ‘no colours at all’)

如果 replacement 参数是一个字符串,那么里边的反斜杠都会被处理。比如 \n 将会被转换成一个换行符,\r 转换成回车,等等。未知的转义如 \j 保持原样。逆向引用如 \6,则被 RE 中相应的捕获组匹配的内容所替换。这使你可以在替换后的字符串中插入一部分原字符串。 

下边例子中,将匹配被 { 和 } 括起来的单词 section,并将 section 替换成 subsection: 

1.>>> p = re.compile(‘section{ ( [^}]* ) }’, re.VERBOSE) 

2.>>> p.sub(r’subsection{\1}’,’section{First} section{second}’) 

3.’subsection{First} subsection{second}’ 

解释:1. 这里开启了 re.VERBOSE,空格将被忽略。因为这里一堆符号,用空格隔开看着才不会乱糟糟的……2. 这里 r’subsection{\1}’ 使用 \1 引用匹配模式中的 ([^}]*) 匹配的字符串内容。

还可以使用 Python 的扩展语法 (?P<*name>…) 指定命名组,引用命名组的语法是 \g<name>。\g<name> 会将名字为 name 的组匹配的字符串替换进去。另外,\g<数字> 是通过组的序号进行引用。\g<2> 其实就相当于 \2,但我们更提倡使用 \g<2>,因为这样可以避免歧义。例如,\g<2>0 的含义是引用序号为 2 的组,然后后边匹配一个字符 ‘0’,而你写成 \20 就会被认为是引用序号为 20 的组了。 

1.>>> p = re.compile(‘section{ (?P<name> [^}]* ) }’, re.VERBOSE) 

2.>>> p.sub(r’subsection{\1}’,’section{First}’) 

3.’subsection{First}’ 

4.>>> p.sub(r’subsection{\g<1>}’,’section{First}’) 

5.’subsection{First}’ 

6.>>> p.sub(r’subsection{\g<name>}’,’section{First}’) 

7.’subsection{First}’

有时候可能不满足简单的字符串替换,replacement 参数还可以是一个函数,该函数将会在正则表达式模式每次不重复匹配的时候被调用。在每次调用时,函数会收到一个匹配对象的参数,因此你就可以利用这个对象去计算出新的字符串并返回它。

下边的例子中,替换函数将十进制数替换为十六进制数: 

1.>>> def hexrepl(match): 

2…. “Return the hex string for a decimal number” 

3…. value = int(match.group()) 

4…. return hex(value) 

5…. 

6.>>> p = re.compile(r’\d+’) 

7.>>> p.sub(hexrepl, ‘Call 65490 for printing, 49152 for user code.’) 

8.’Call 0xffd2 for printing, 0xc000 for user code.’

当使用模块级的 re.sub() 函数时,正则表达式模式作为第一个参数。该模式可以是一个字符串或一个编译好的对象。如果你需要指定正则表达式标志,那么你必须使用后者;或者使用模式内嵌修正器,例如 sub(“(?i)b+”, “x”, “bbbb BBBB”) 返回 ‘x x’。

注意事项:

使用字符串方法 

有时使用 re 模块是个错误!如果你匹配一个固定的字符串或者单个字符类,并且你没有使用 re 的任何标志(像 IGNORECASE 标志),那么就没有必要使用正则表达式了。字符串有一些方法是对固定字符串进行操作的,并且它们通常比较快。因为它们都是独立优化的 C 语言小循环,目的是在简单的情况下代替功能更加强大、更具通用性的正则表达式引擎。 

举个例子,例如你想把字符串中所有的 dead 替换成 word,你会想到使用正则表达式的 re.sub() 方法来实现,但这么简单的替换,还是考虑直接使用字符串的 replace() 方法吧。但有一点你需要注意,就是 replace() 会在单词里边进行替换,像 swordfish 会变成 sdeedfish,这显然不是你想要的!replace() 没办法识别单词的边界,因此你才来考虑使用正则表达式。只需要将 RE 的模式写成 \bword\b 即可胜任此任务。 

另一个常见的情况是从一个字符串中删除单个字符或者用另一个字符替代它。你也许会想到用 re.sub(‘\n’, ’ ‘, S) 这样的正则表达式来实现,但其实字符的 translate() 方法完全能够胜任这个任务,并且比任何正则表达式操作起来更快些。 

简而言之,在使用 re 模块之前,先考虑一下你的问题是否可以用更快速、简单的字符串自带方法来解决。

match() VS search() 

match() 函数只会检查 RE 是否在字符串的开始处匹配,而 search() 会遍历整个字符串搜索匹配的内容。记住这一区别很重要。再次强调一下,match() 只会报告一次成功的匹配,并且匹配的位置必须是从字符串的第一个字符开始: 

1.>>> print(re.match(‘super’, ‘superstition’).span()) 

2.(0, 5) 

3.>>> print(re.match(‘super’, ‘insuperable’)) 

4.None 

另一方面,search() 函数将遍历整个字符串,并报告它找到的第一个匹配: 

1.>>> print(re.search(‘super’, ‘superstition’).span()) 

2.(0, 5) 

3.>>> print(re.search(‘super’, ‘insuperable’).span()) 

4.(2, 7) 

一般分析会先找到匹配的第一个字符是什么。举个例子,模式 Crow 必须从字符 ‘C’ 开始匹配,那么匹配引擎分析后会快速遍历字符串,然后在 ‘C’ 被找到之后才开始全部匹配。 

添加一个 .* 会导致这个优化失败,请使用 re.search() 代替。

贪婪 VS 非贪婪 

当重复一个正则表达式时,如果使用 a*,那么结果是尽可能多地去匹配。当你尝试匹配一对对称的定界符,例如 HTML 标志中的尖括号,默认的贪婪模式会使得你很困扰。 

eg: 

1.>>> s = ‘<html><head><title>Title</title>’ 

2.>>> len(s) 

3.32 

4.>>> print(re.match(‘<.*>’, s).span()) 

5.(0, 32) 

6.>>> print(re.match(‘<.*>’, s).group()) 

7.<html><head><title>Title</title

RE 匹配在 <html> 的 < 后,.* 消耗掉字符串的剩余部分。由于正则表达式默认是贪婪的原因,RE 必须从字符串的尾部一个字符一个字符地回溯,直到找到匹配的 >。大家看到,按照这种方法,最后找到匹配内容竟是 的 < 开始,到 的 > 结束。显然这不是你想要的结果。 

在这种情况下,解决方案是使用非贪婪的限定符 *?、+?、?? 或 {m,n}?,尽可能地匹配小的文本。 

1.>>> print(re.match(‘<.*?>’, s).group()) 

2.<html

在上边的例子中,> 在第一个 < 被匹配后立刻尝试匹配,如果失败,匹配引擎前进一步,尝试下一个字符,直到第一次匹配 >,这样就得到了我们想要的结果。 

注意,使用正则表达式分析 HTML 和 XML 是很痛苦的。当你编写一个正则表达式去处理所有可能的情况时,你会发现 HTML 和 XML 总会打破你的“规则”,这让你很头疼……像这样的话,建议使用 HTML 和 XML 解析器来处理更合适。 

re的贪婪模式会尽可能多的匹配

使用 re.VERBOSE 

正则表达式的表示非常紧凑。这也带来了一个问题,就是不好阅读。当编译正则表达式时指定 re.VERBOSE 标志是非常有帮助的 

re.VERBOSE 标志有几个作用。在正则表达式中不在字符类中的空白字符将被忽略。这就意味着像 I love FishC 这样的表达式和可读性较差的 IloveFishC 相同。但 [a b] 将匹配字符 ‘a’、’b’ 或 ’ ‘;另外,你也可以把注释放到 RE 中,注释是从 # 开始到下一行。当使用三引号字符串时,会使得 REs 的格式更整洁: 

pat = re.compile(r”“” 

\s* # Skip leading whitespace 

(?P<header>[^:]+) # Header name 

\s* : # Whitespace, and a colon 

(?P<value>.?) # The header’s value – ? used to 

# lose the following trailing whitespace 

\s*$ # Trailing whitespace to end-of-line 

“”“, re.VERBOSE) 

同样的内容,下边这个要难读得多:

pat = re.compile(r”\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s$”)

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

推荐阅读更多精彩内容