正则表达式(Regular Expression,简称regex)是一种强大的文本处理工具,用于在字符串中查找、匹配和替换特定模式的文本。
Python 通过re
模块提供完整的正则表达式支持,包含以下核心功能:
def isPhoneNumber(text):
if len(text) !=12:
return False
for i in range(3):
if not text[i].isdecimal():
return False
if text[3]!='-':
return False
for i in range(4,7):
if not text[i].isdecimal():
return False
if text[7] != '-':
return False
for i in range(8,12):
if not text[i].isdecimal():
return False
return True
print('415-555-4242 is a phone number:')
415-555-4242 is a phone number:
print(isPhoneNumber('415-555-4242'))
True
print('Moshi moshi is a phone number:')
Moshi moshi is a phone number:
print(isPhoneNumber('Moshi moshi'))
False
isPhoneNumber()
函数的代码进行几项检查,
看看 text
中的字符串是不是有效的电话号码。
如果其中任意一项检查失败,函数就返回 False
。
代码首先检查该字符串是否刚好有12个字符。
然后它检查区号(就是 text
中的前3个字符)是否只包含数字。
函数剩下的部分检查该字符串是否符合电话号码的模式:
号码必须在区号后出现第一个短横线),
3个数字,然后是另一个短横线,最后是4个数字。
如果程序执行通过了所有的检查,它就返回 True
。
用参数 '415-555-4242'
调用 isPhoneNumber()
将返回真。
用参数 'Moshimoshi'
调用 isPhoneNumber()
将返回假,
第一项测试失败了,因为不是12个字符。
必须添加更多代码,才能在更长的字符串中寻找这种文本模式。
用下面的代码,
替代 isPhoneNumber.py
中最后4个 print()
函数调用:
message='Call me at 415-555-1011 tomorrow.415-555-9999 is my office.'
for i in range(len(message)):
chunk=message[i:i+12]
if isPhoneNumber(chunk):
print('Phone number found:' + chunk)
print('Done')
Phone number found:415-555-1011 Phone number found:415-555-9999 Done
该程序运行时,输出看起来像这样:
在 for
循环的每一次迭代中,
取自 message
的一段新的12个字符被赋给变量 chunk()
。
例如,在第一次迭代, i
是 0
,
chunk
被赋值为 message[0:12]
(即字符串'Call me at 4'
)。在下一次迭代,
i
是1
, chunk
被赋值为
message[1:13]
(字符串 'all me at 41'
)。
将 chunk
传递给 isPhoneNumber()
,
看看它是否符合电话号码的模式。如果符合,
就打印出这段文本。
继续遍历 message
,最终 chunk
中的12个字符会是一个电话号码。
该循环遍历了整个字符串,测试了每一段12个字符,
打印出所有满足 isPhoneNumber()
的 chunk
。
当遍历完 message
,就打印出 Done
。
在这个例子中,虽然 message
中的字符串很短,
但它也可能包含上百万个字符,程序运行仍然不需要一秒钟。
使用正则表达式查找电话号码的类似程序,
运行也不会超过一秒钟,但用正则表达式编写这类程序会快得多。
使用正则表达式查找
如果想在有分机的电话号码中快速查找电话号,例如415-555-4242 x99,该怎么办呢?
正则表达式,又称规则表达式,英语简称为 regex
,是文本模式的描述方法,
能帮助方便的检查一个字符串是否与某种模式匹配。
例如,\d
是一个正则表达式,表示一位数字字符,
即任何一位0到9的数字。 Python 使用正则表达式 \d\d\d-\d\d\d-\d\d\d\d
,
来匹配前面 isPhoneNumber()
函数匹配的同样文本:
3个数字、一个短横线、3个数字、一个短横线、4个数字。
所有其他字符串都不能匹配
\d\d\d-\d\d\d-\d\d\d\d
正则表达式。
但正则表达式可以复杂得多。例如,
在一个模式后加上花括号包围的3 ({3}
),
就是说,“匹配这个模式3次”。
所以较短的正则表达式 \d{3}-\d{3}-\d{4}
,
也匹配正确的电话号码格式。
import re
resobj = re.search('\d\d\d-\d\d\d-\d\d\d\d','My number is 415-555-4242.')
<>:1: SyntaxWarning: invalid escape sequence '\d' <>:1: SyntaxWarning: invalid escape sequence '\d' /tmp/ipykernel_4356/2287721066.py:1: SyntaxWarning: invalid escape sequence '\d' resobj = re.search('\d\d\d-\d\d\d-\d\d\d\d','My number is 415-555-4242.')
resobj.span()
(13, 25)
resobj.group()
'415-555-4242'
使用正则表达式修改上面的函数,得到第2个版本的函数 isPhoneNumber2()
。
运行如下:
def isPhoneNumber2(text):
if len(text) !=12:
return False
res_ob = re.search('\d\d\d-\d\d\d-\d\d\d\d',text)
if res_ob:
return True
return False
<>:4: SyntaxWarning: invalid escape sequence '\d' <>:4: SyntaxWarning: invalid escape sequence '\d' /tmp/ipykernel_4356/381155936.py:4: SyntaxWarning: invalid escape sequence '\d' res_ob = re.search('\d\d\d-\d\d\d-\d\d\d\d',text)
print('415-555-4242 is a phone number:')
415-555-4242 is a phone number:
print(isPhoneNumber2('415-555-4242'))
True
print('Moshi moshi is a phone number:')
Moshi moshi is a phone number:
print(isPhoneNumber2('Moshi moshi'))
False
对比两个函数的写法。 不论是可读性还是代码量,使用正则表达式都要好得的。
模式 | 描述
^
匹配字符串的开头。$
匹配字符串的末尾。.
匹配任意字符,除了换行符,当re.DOTALL
标记被指定时,则可以匹配包括换行符的任意字符。[...]
用来表示一组字符,单独列出:[amk]
匹配'a'
,'m'
或'k'
。[^...]
不在[]
中的字符:[^abc]
匹配除了a,b,c之外的字符。re*
匹配0个或多个的表达式。re+
匹配1个或多个的表达式。re?
匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式。re{ n}
re{ n,}
精确匹配n个前面表达式。re{ n, m}
匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式。a|b
匹配a或b。(re)
G匹配括号内的表达式,也表示一个组。(?imx)
正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。(?-imx)
正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。(?: re)
类似 (...
), 但是不表示一个组。(?imx:re)
在括号中使用i, m, 或 x 可选标志。(?-imx: re)
在括号中不使用i, m, 或 x 可选标志。(?#...)
注释。(?= re)
前向肯定界定符。如果所含正则表达式,以...
表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。(?! re)
前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功。(?> re)
匹配的独立模式,省去回溯。\w
匹配字母数字。\W
匹配非字母数字。\s
匹配任意空白字符,等价于[\t\n\r\f]
。\S
匹配任意非空字符\d
匹配任意数字,等价于[0-9]
。\D
匹配任意非数字。\A
匹配字符串开始。\Z
匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。\z
匹配字符串结束。\G
匹配最后匹配完成的位置。\b
匹配一个单词边界,也就是指单词和空格间的位置。例如,er\b
可以匹配never
中的er
, 但不能匹配verb
中的er
。\B
匹配非单词边界。'er\B'
能匹配"verb"
中的'er'
,但不能匹配"never"
中的'er'
。\n,
\t
匹配一个换行符。匹配一个制表符。\1...\9
匹配第n
个分组的内容。\10
匹配第n
个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。
这里快速复习一下学到的内容:
?
匹配零次或一次前面的分组。*
匹配零次或多次前面的分组。+
匹配一次或多次前面的分组。{n}
匹配n
次前面的分组。{n,}
匹配n
次或更多前面的分组。{,m}
匹配零次到m
次前面的分组。{n,m}
匹配至少n
次、至多m
次前面的分组。{n,m}?
或*?
或+?
对前面的分组进行非贪心匹配。^spam
意味着字符串必须以spam
开始。spam$
意味着字符串必须以spam
结束。.
匹配所有字符,换行符除外。\d
、\w
和\s
分别匹配数字、单词和空格。\D
、\W
和\S
分别匹配出数字、单词和空格外的所有字符。[abc]
匹配方括号内的任意字符(诸如a、b或c)。[^abc]
匹配不在方括号内的任意字符。
line = "Cats are smarter than dogs";
searchObj = re.search('(.*) are (.*?) .*', line, re.M|re.I)
if searchObj:
print ("searchObj.group() : ", searchObj.group())
print ("searchObj.group(1) : ", searchObj.group(1))
print ("searchObj.group(2) : ", searchObj.group(2))
else:
print ("Nothing found!!")
searchObj.group() : Cats are smarter than dogs searchObj.group(1) : Cats searchObj.group(2) : smarter