Python Fifth Step: 正则表达式

Jan 2, 2018


本文

http://afra55.github.io/2018/01/02/python-fifth-step/

正则表达式

正则表达式是包含文本和特殊字符的字符串,该字符串描述一个可以识别各种字符串的模式

常见正则表达式符号和特殊字符

符号 说明 示例
literal 匹配文本字符串的字面值 literal foo
text1|text2 匹配正则表达式 text1 或 text2 one|One
. 匹配任何字符(除了\n之外) b.b
^ 匹配字符串起始部分 ^Lover
$ 匹配字符串终止部分 /bin/*sh$
* 匹配 0 次或多次前面出现的正则表达式 [A-Za-z0-9]*
+ 匹配 1 次或多次前面出现的正则表达式 [a-z]+\.com
? 匹配 0 次或 1 次前面出现的正则表达式 goo?
{N} 匹配 N 次前面出现的正则表达式 [0-9]{7}
{M,N} 匹配 M~N 次前面出现的正则表达式 [0-9]{5,7}
[...] 匹配来自字符集的任意单一字符 [aeiou]
[..x-y..] 匹配 x~y 范围中的任意单一字符 [0-9],[A-Za-z]
[^...] 不匹配此字符集中出现的任一字符,包括在该字符集中出现的某一范围的字符 [^aeiou],[^A-Za-z0-9]
(*|+|?|{})? 用于匹配上面频繁出现或重复出现符号的非贪婪版本(*, +, ?, {}), 例如 \*(.+?)\*匹配*abc*def*中的*abc*而不是*abc*def* .*?[a-z]
(…) 匹配封闭的正则表达式,然后另存为子组 ([0-9]{3})?,f(oo|u)bar
特殊字符 说明 示例
\d 匹配任何十进制数字,与 [0-9] 一致, \D\d 相反意思是匹配一个非数字字符。等价于[^0-9] data\d+.txt
\w 匹配任何字母数字字符, 与 [A-Za-z0-9] 一致(\W 意义相反) [A-Za-z_]\w+
\s 匹配任何空格字符,与 [\n\t\r\v\f] 相同(\S 意义相反) of\sthe
\b 匹配任何单词边界,也就是指单词和空格间的位置, 例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”(\B意义相反) \bThe\b
\N 匹配已保存的子组 N (参见 (...)) price:\16
\c 逐字匹配任何特殊字符c (仅按照字面意义匹配,不匹配特殊含义) \.,\\,\*
\A(\Z) 匹配字符串的起始(结束)(参见^, $) \ALover
扩展表示法 说明 示例
(?iLmsux) 在正则表达式中嵌入一个或多个特殊“标记”参数(或函数/方法) (?i), (?x), (?im)
(?:...) 表示一个匹配不用保存的分组 (?:\w+\.)*
(?P<name>)... 表示一个仅由 name 标识而不是数字标识的正则分组 (?P<data>)
(?P=name) 在同一字符串中匹配之前的 (?P<name>) 正则分组 (?P=data)
(?#...) 表示注释,所有内容都被忽略 (?#comment)
(?=...) 正向前视断言: 例如,“Windows(?=7|8|9|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows10”中的“Windows” (?=.com)
(?!=...) 负向前视断言: 例如,“Windows(?!7|8|9|2000)”能匹配“Windows10”中的“Windows”,但不能匹配“Windows2000”中的“Windows” (?!=.net)
(?<=...) 正向后视断言: 例如,“(?<=7|8|9|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“10Windows”中的“Windows” (?<=800-)
(?<!...) 负向后视断言:例如,”(?<!2000)Windows” 能匹配”10Windows” 中的 “Windows”, 但不能匹配 “2000Windows” 中的”windows” (?<!192\.168\.)
(?(id/name)Y|N) 如果分组所提供的 id 或 name 存在,就返回正则表达式的条件匹配Y, 如果不存在就返回 N (|N 是可选的) (?(1)y|x)

择一匹配

| 是表示择一匹配(并,也可称作或)的符号, 即从多个模式中选择其一

比如 你|我|他 匹配的字符串是 ‘你’、’我’、’他’

字符集

由方括号包含 [...], 能够匹配一对方括号中包含的任何字符

b[aeiou]t 匹配 ‘bat’,’bet’,’bit’,’bot’,’but’

方括号中,两个字符用 - 连字符连接,代表字符的范围

A-Z,a-z,0-9 分别表示大写字母,小写字母,数字

[^...] 表示不匹配字符集中的任一字符

闭包

闭包,匹配一个或多个或没有出现的字符模式

Kleene 闭包: *

正闭包操作符:+

?, {}

使用分组操作符时,正则表达式引擎会尽可能的扩大匹配的范围,这叫做贪婪匹配

? 可以避免贪婪匹配

分组

使用圆括号指定分组

\d+(\.\d*)? 任何十进制数字后面可以接一个小数点和零个或多个数字,例如 0.003, 3, 33.

使用 Python 写正则

Python 通过 re 模块来支持正则表达式

re 模块函数 说明
compile(pattern, flags=0) 使用可选的标记(flags)来编译正则表达式的模式,并返回一个正则表达式对象
re 模块函数和正则表达式对象的方法 说明
match(pattern, string, flags=0) 使用带有可选标记(flags)的正则表达式对象来匹配字符串,如果匹配成功则返回匹配对象,否则返回 None
search(pattern, string, flags=0) 使用可选标记搜索字符串中第一次出现的正则表达式模式,如果匹配成功则返回匹配对象,否则返回 None
findall(pattern, string, flags=0) 查找字符串中所有(非重复)出现的正则表达式模式,并返回一个匹配列表
finditer(pattern, string, flags=0) 与 findall() 函数相同,但返回的是一个迭代器,对每一次匹配,迭代器都返回一个匹配对象
split(pattern, string, maxsplit=0, flags=0) 根据正则表达式的模式分隔符, 将字符串分隔为列表, 然后返回成功匹配的列表,分隔最多操作 maxsplite 次(默认分隔所有匹配成功的位置)
sub(pattern, repl, string, count=0, flags=0) 使用 repl 替换所有正则表达式模式在字符串中出现的位置, 除非定义 count 否则替换所有位置(subn()函数返回替换操作的数目)
purge() 清除隐式编译的正则表达式模式
常用的匹配对象方法 说明
group(self, *args) 默认返回整个匹配对象,如果传入数字(group(num=0))则子组模式的匹配对象,默认是0即整个匹配对象,如果传入多个参数(group(0, 1, 2)则返回一个元组包含传入编号的子组的匹配对象
groups(self, default=None) 返回一个包含所有匹配子组的元组,如果没有匹配成功,则返回空元组
groupdict(self, default=None) 返回一个包含所有匹配的命名子组的字典,所有子组名称作为字典的键,如果没有成功匹配,则反回一个空字典
常用的模块属性(用于正则表达式的标记 flags, (?iLmsux)) 说明
re.I re.IGNORECASE 不区分大小写匹配
re.L re.LOCALE 根据所使用的本地语言环境通过 \w,\W,\b,\B,\s,\S 实现匹配
re.M re.MULTILINE ^$分别匹配目标字符串中行的起始和结尾,而不是严格匹配整个字符串本身的起始和结尾
re.S re.DOTALL 表示 .点能够匹配所有字符包括 \n 换行符
re.X re.VERBOSE 通过反斜杠转义,否则所有空格和#(以及该行中所有的后续文字)都被忽略,除非在字符类中或者允许注释且提高可读性

match() 函数从字符串起始部分对模式进行匹配,如果匹配成功返回一个匹配对象,匹配失败返回None

m = re.match('victor', 'victor')    # 匹配成功,则返回一个匹配对象 <_sre.SRE_Match object; span=(0, 6), match='victor'>
if m is not None:
    m.group()   # victor, 返回匹配的字符串

search() 在字符串中查找模式, 当需要匹配的模式出现在字符串中间时(‘aavictor’) 则 match() 会匹配失败,search() 会在任意位置对正则表达式的模式进行匹配,如果搜索到成功的匹配,就会返回一个匹配对象, 否则返回 None

m = re.match('victor', 'i am victor')   # None
if m is not None:
    print(m.group())
print('--')
s = re.search('victor', 'i am victor')  # <_sre.SRE_Match object; span=(5, 11), match='victor'>
if s is not None:
    print(s.group())    # victor

子组

m = re.match('(\w\w\w)-(\d\d\d)', 'abc-123')
if m is not None:
    print(m.group(), m.group(1), m.group(2))    # abc-123 abc 123
    print(m.groups())                           # ('abc', '123')

findall()

m = re.findall('victor', 'victor i am victor yvictor')
if m is not None:
    print(m)         # ['victor', 'victor', 'victor']   

sub() subn()

m = re.sub('X', 'Victor', 'I am X, i love X')
if m is not None:
    print(m)        # I am Victor, i love Victor

m = re.subn('X', 'Victor', 'I am X, i love X')
if m is not None:
    print(m)        # ('I am Victor, i love Victor', 2)

m = re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})', r'\2/\1/\3', '20/3/2018')  # \1, \2, \3 分别代表分组1,2,3,即前面模式中圆括号扩起来的
if m is not None:
    print(m)        # 3/20/2018

split()

m = re.split(':', '1:2:3:and')
if m is not None:
    print(m)    # ['1', '2', '3', 'and']




data = (
    'A, 129',
    'B, 230',
    'C, 409',
    'D MN',
    'E HU'
)
for d in data:
    m = re.split(', | (?=(?:\d{3}|[A-Z]{2}))', d)
    if m is not None:
        print(m)

"""
输出
['A', '129']
['B', '230']
['C', '409']
['D', 'MN']
['E', 'HU']
"""

扩展符号

(?iLmsux)

m = re.findall('yes', 'yes, Yes, YES')
if m is not None:
    print(m)    # ['yes']

m = re.findall('(?i)yes', 'yes, Yes, YES')
if m is not None:
    print(m)    # ['yes', 'Yes', 'YES']

m = re.findall('yes', 'yes, Yes, YES', flags=re.I)
if m is not None:
    print(m)    # ['yes', 'Yes', 'YES']

m = re.findall('(?im)(^th[\w]+)', """
The me is that.
That what i have you.
th hh h h.
""")
if m is not None:
    print(m)    # ['The', 'That']

原始字符串差异, 如果 有 符号 同时 用于 ASCII 和 正 则 表达式,则需要使用原始字符串来避免问题,例如: r'\b'

m = re.match('\byes', 'yes')    # \b 退格符号
if m is not None:
    print(m.group())    # None

m = re.match('\\byes', 'yes')   # \\b 正则表达式
if m is not None:
    print(m.group())    # yes

m = re.match(r'\byes', 'yes')   # \b 正则表达式
if m is not None:
    print(m.group())    # yes