正则表达式,用一个特殊模式去匹配字符串的一部分,其中依据特定的规则建立的特殊模式,就是正则表达式。
一、为什么要用原始字符串
1、Python 解释运行正则表达式的过程
正则表达式不是核心Python语言的一部分,Python 没有创建用于正则表达式的特殊语法。在Python中专门用于处理正则表达式的是 标准库 re 模块,它只是Python附带的C 语言扩展模块。也就是说,从本质上讲,正则表达式是嵌入在Python中的一种微小的、高度专业化的编程语言。
Python 解释运行正则表达式的过程是:
第一步、Python 解释运行程序读取程序文件中书写的正则表达式。
第二步、将读取的正则表达式以“字符串的形式”传给 re 模块的相关函数。
第三步、re 模块的相关函数将正则表达式编译成一系列字节码,调用 C 语言编写的匹配引擎执行。
第四步、re 模块的相关函数返回匹配引擎执行结果。
2、原始字符串
上面解释运行过程的第一步对于普通的字符是没有问题的,例如,Python 程序文件中书写的正则表达式为“abc”,那么,Python 解释运行程序传给re 模块的相关函数的也会是字符串“abc”,但是,对于反斜杠“\”时就会有一点点麻烦。
现在假设需要用正则表达式匹配字符串“ab\cd”,也就是说需要匹配一个反斜杠“\”。按照正则表达式语法,匹配一个反斜杠“\”需要用两个反斜杠,即,匹配字符串“ab\cd”所需要的正则表达式为:ab\cd,也就是, re 模块的相关函数需要接收的字符串为:ab\cd。
为了以上的匹配,在Python 程序文件中直接将正则表达式书写为“ab\cd”行不行呢?不行。因为如果写成这样,Python 解释运行程序在读取时会按照Python 的语法,将“\”解释为“\”,即,“ab\cd”在Python 解释后实际为“ab\cd”,这样Python 解释运行程序传给 re 模块的相关函数的字符串也就为:ab\cd,而不是 re 模块的相关函数所需要的:ab\cd。
所以,为了使用正则表达式去匹配字符串“ab\cd”,在Python 程序文件中需要将正则表达式书写为:ab\\cd。这样反复书写反斜杠“\”显然很容易引起混乱,为此,Python 创建了一个语法规则,也是 Python 专门为正则表达式设置了唯一的一个语法规则:原始字符串。
原始字符串,即是在一般字符串前加上字母r或R。例如:r’aaa’、R’\bxyz\n’。原始字符串中所有字符都不再进行转义。也就是说,Python 解释运行程序在碰到原始字符串时,不会再按照Python 的语法去解释它,而是原封不动地把它传给 re 模块的相关函数,这样,在原始字符串中,就可完全按照正则表达式的语法去书写匹配用的表达式了。例如,要匹配字符串“ab\cd”,直接按照正则表达式的语法写成:ab\cd,然后加r或R变成原始字符串: r“ab\cd” 或 R“ab\cd”。
在Python 程序中的正则表达式,一般都应该用原始字符串来表示。
二、正则表达式专用符号
1、基本字符
. ———— 匹配除换行符 \n 之外的任何单字符
* ———— 0次或多次,“.*” 表示任意字符串(包括空字符)
+ ———— 1次或多次,“.+” 空字符之外的任意字符串
? ———— 0次或1次,“.?” 任意单个字符(包括空字符)
| ———— 或,先级很低。例如:“ab|dc” 的意思是:ab 或 cd。
例如:
from re import findall
x='abcdabcdefgefg'
print(findall(r'.*',x)) # ['abcdabcdefgefg', '']
print(findall(r'.+',x)) # ['abcdabcdefgefg']
print(findall(r'.?',x)) # ['a','b','c','d','a','b','c','d','e','f','g','e','f','g','']
print(findall(r'da|ge',x)) # ['da', 'ge']
2、常规转义符号
\\ ———— 匹配反斜杠 \
\t ———— 匹配一个水平制表符
\v ———— 匹配一个垂直制表符
\f ———— 匹配一个换页符
\n ———— 匹配一个换行符
\r ———— 匹配一个回车符
例如:
from re import findall
x='abc\tDEf\vgh\fi:j\n\kL--mn\ropq'
print(findall(r'\\.+?.+?',x)) # ['\\kL']
print(findall(r'\t.+?.+?',x)) # ['\tDE']
print(findall(r'\v.+?.+?',x)) # ['\x0bgh']
print(findall(r'\f.+?.+?',x)) # ['\x0ci:']
print(findall(r'\n.+?.+?',x)) # ['\n\\k']
print(findall(r'\r.+?.+?',x)) # ['\rop']
3、字符集 [ ]
[ ] 用于匹配一组字符。在[ ]中可以单独列出字符,也可以用“起始符-终止符”来标记某一范围内的字符。 例如,[abc] 将匹配字符 a、或b、或c。[a-c] 与 [abc] 等同。
特别要注意的是,不论字符集 [ ]中内容有多少,它不表示字符串,而仅表示某一个字符,这个字符是哪个不确定,但在数量上一定是一个。
“. *、 +、?、$” 等匹配用字符在字符集中不生效。也就是说,这些匹配用字符在字符集被剥夺了特殊性,等同于普通字符。
‘^’ 放在字符集 [ ]开头,有特殊的含义,表示“非”。例如,[^xyz] 表示任意一个非x,也非y,也非z 的字符。如果’^’ 不是出现在字符集 [ ]开头,则它没有特殊含义,仅表示它本身。 例如:[x^] 将匹配“x” 或“^”。
. ^ * +
[0-9] ———— 匹配任意数字,等价于[0123456789]
[a-z] ———— 匹配任意小写字母
[A-Z] ———— 匹配任意大写字母
[a-zA-Z] ———— 匹配任意大小写字母
[a-zA-Z0-9] ———— 匹配任意大小写字母和数字
例如:
from re import findall
x='abc\tDEf\vgh*123?xyz.456'
print(findall(r'[abcdxyz]',x)) # ['a', 'b', 'c', 'x', 'y', 'z']
print(findall(r'[1-9]',x)) # ['1', '2', '3', '4', '5', '6']
print(findall(r'[^a-zA-Z0-9]',x)) # ['\t', '\x0b', '*', '?', '.']
print(findall(r'[*.]\w\w',x)) # ['*12', '.45']
print(findall(r'\w\w[*.]',x)) # ['gh*', 'yz.']
4、预定义字符集
\d ———— 匹配一个数字字符,等价于 [0-9]
\D ———— 匹配一个非数字字符,等价于 [^0-9]
\s ———— 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [\f\n\r\t\v]
\S ———— 匹配任何非空白字符。等价于 [^\f\n\r\t\v]。
\w ———— 匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'
\W ———— 匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'
例如:
from re import findall
x='abc\tDEf\vgh\fi:j\n\kL--mn\ropq123r_s_t456u"vw"789x_yz'
print(findall(r'\d.+?.+?',x)) # ['123', '456', '789']
print(findall(r'\D.+?.+?',x)) # ['abc','\tDE','f\x0bg','h\x0ci','\n\\k','L--','mn\r','opq','r_s','_t4','u"v','w"7','x_y']
print(findall(r'\s.+?.+?',x)) # ['\tDE', '\x0bgh', '\x0ci:', '\n\\k', '\rop']
print(findall(r'\S.+?.+?',x)) # ['abc','DEf','gh\x0c','i:j','\\kL','--m','n\ro','pq1','23r','_s_','t45','6u"','vw"','789','x_y']
print(findall(r'\w.+?.+?',x)) # ['abc','DEf','gh\x0c','i:j','kL-','mn\r','opq','123','r_s','_t4','56u','vw"','789','x_y']
print(findall(r'\W.+?.+?',x)) # ['\tDE', '\x0bgh', '\x0ci:', '\n\\k', '--m', '\rop', '"vw', '"78']
5、重复标记 { }
{n} ———— 次数 = n
{n,} ———— 次数 >= n
{,m} ———— 次数 <= m(包括空字符)
{n,m} ———— n <= 次数 <= m
{0,} ———— 等价于 *
{1,} ———— 等价于 +
{0,1} ———— 等价于 ?
例如:
from re import findall
x='abc\tDEf\vgh\fi:j\n\kL--mn\ropq123'
print(findall(r'\w{3}',x)) # ['abc', 'DEf', 'opq', '123']
print(findall(r'\w{3,}',x)) # ['abc', 'DEf', 'opq123']
print(findall(r'\w{,5}',x)) # ['abc','','DEf','','gh','','i','','j','','','kL','','','mn','','opq12','3','']
print(findall(r'\w{3,5}',x)) # ['abc', 'DEf', 'opq12']
6、位置标记
因为位置标记仅用于指定匹配的位置,匹配的内容无任何“宽度”,所以又被称作:零宽度断言。
^ ———— 行首。单行时,匹配字符串的开头;多行时,在每个换行符后立即匹配。
$ ———— 行尾。匹配字符串的结尾,或者后跟换行符的任何位置。
\A ———— 匹配字符串的开头,单行时,等同于 ^ 。
\Z ———— 匹配字符串结尾,单行时,等同于 $ 。
\b ———— 匹配单词边界,即单词头或单词尾。
\B ———— 匹配非单词边界。
from re import findall
x='abc\tDEf\vgh\fi:j\n\kL--mn\ropq123r_s_t456u"vw"789x_yz'
print(findall(r'^\w\w',x)) # ['ab']
print(findall(r'\w\w$',x)) # ['yz']
print(findall(r'\A\w\w',x)) # ['ab']
print(findall(r'\w\w\Z',x)) # ['yz']
print(findall(r'\b.+?.+?',x)) # ['ab','\tD','\x0bg','\x0ci',':j','kL','--','mn','\ro','"v','"7']
print(findall(r'\B.+?.+?',x)) # ['bc','Ef','h\x0c','\\k','L-','-m','n\r','pq','12','3r','_s','_t','45','6u','w"','89','x_','yz']
三、分组 ( )
在正则表达式中,可用小括号( )把部分内容括起来作为一个整体看待。每个用小括号( )括起来的部分又称作一个“分组”。当正则表达式中有分组时,系统会为每个分组按照左括号的出现的次序、从左到右、从1开始分配组号。分组0对应整个正则表达式。
import re
p = re.compile(r'(a\w)\w+(d\w)\w+(g\w)\w+(j\w)')
m = p.search('abcdefghijk')
print(m) # match='abcdefghijk'
print(m.group()) # abcdefghijk
print(m.group(0)) # abcdefghijk
print(m.group(1)) # ab
print(m