在登录某些网站时,需要填写邮箱等相关信息,在邮箱格式填错时,页面总能第一时间检出并告知用户邮箱格式有误,那么程序员是使用什么方法实现这种检测的呢?一般会使用正则表达式。正则表达式定义了字符串的匹配模式,即如何检查一个字符串是否有跟某种模式匹配的部分或者从一个字符串中将与模式匹配的部分提取出来或者替换掉。Python 也提供了对正则表达式操作的支持, Python 通过标准库中的 re 模块来支持正则表达式操作。
1
查找第一次出现的匹配文本
首先,通过一个例子来看看如何在 Python 中使用正则表达式查找文本模式。假设我们需要在一个字符串中查找是否包含国内的一个座机号码,格式为 XXX-XXXXXXX ,即三个数字表示区号,加上一个短横杠,加上七个数字表示座机号。
# 导入正则表达式模块
import re
if __name__ == '__main__':
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d\d\d\d\d')
mo = phoneNumRegex.search('My number is 020-1234567.')
print('Phone number is: ' + mo.group())
最终输出为
Phone number is: 020-1234567 |
我们来解释一下上面的代码。Python 中的所有正则表达式的函数都在 re 模块中,所以使用正则表达式时一定要导入 re 模块。在程序中调用 compile() 方法,通过传入一个正则表达式字符串,返回一个 Regex 模式对象。通过这个模式对象调用 search() 方法,传入字符串,以查找正则表达式的所有匹配。如果在此字符串没有找到匹配正则表达式的文本,该方法会返回 None 。如果找到了匹配项,该方法会返回一个 Match 对象。Match 对象中有 group() 方法,它返回被查找字符串中实际匹配的文本。在这个例子中,我们将正则表达式传入 re.compile() ,\d代表一个数字字符,并将得到的 Regex 对象保存到 phoneNumRegex 中,通过调用 search() 方法,传入想要进行查找的文本,把查找的结果放在 mo 变量中,因为文本里面刚好有匹配的文本,所以最终返回了一个 Match 对象,最后通过调用 group() 方法返回匹配的结果。
2
匹配文本的不同部分
如果我们想要把区号和座机号进行分离,可以在正则表达式中添加括号以示分组,从而获得匹配文本的不同部分。
import re
if __name__ == '__main__':
phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d\d\d\d\d)')
mo = phoneNumRegex.search('My number is 020-1234567.')
print('Phone number is: ' + mo.group())
print(mo.groups())
print('Area code: ' + mo.group(1))
print('Phone number: ' + mo.group(2))
最终输出为
Phone number is: 020-1234567 ('020', '1234567') Area code: 020 Phone number: 1234567 |
可以看到,我们使用了两对括号对区号和座机号进行了分组。调用 groups() 方法可以一次性获取到所有的分组,这些分组的值存放在元组中。向 group() 方法传入 0 或者 不传入参数,将返回整个匹配的文本;而传入组号则能获取相应组的文本(组号从1开始)。
3
匹配特殊的字符
在正则表达式中,下面的字符都具有特殊的含义:
. ^ $ * + ? { } [ ] \ | ( ) |
如果要匹配实际的字符,需要加 \ 对其进行转义。
\. \^ \$ \* \+ \? \{ \} \[ \] \\ \| \( \) |
下面的例子演示了如何在文本中匹配特殊的字符。
import re
if __name__ == '__main__':
regex = re.compile(r'\(1\+2\)')
mo = regex.search('3*(1+2)=9')
print(mo.group())
最终输出为
(1+2) |
4
用管道匹配多个项中的一个
如果希望匹配多个项中的一个时,可以使用管道符号 | 。使用此符号只能匹配第一次出现的匹配文本。下面的例子演示了如何匹配 'Mary' 或 'Tom' 其中的一个。
import re
if __name__ == '__main__':
regex = re.compile(r'Mary|Tom')
mo = regex.search('Hello, I am Mary. This is my friend Tom.')
print(mo.group())
最终输出为第一次出现的匹配文本
Mary |
5
用问号匹配可选项
如果你希望匹配一段文本,但其中部分字符是可以存在也可以不存在的,可以使用 ? 。? 表明前面的分组在这个模式中是可选的。
import re
if __name__ == '__main__':
regex = re.compile(r'B(an)?')
mo1 = regex.search('B')
print(mo1.group())
mo2 = regex.search('Ban')
print(mo2.group())
最终输出为
B Ban |
6
用星号匹配零次或多次出现的文本
如果你希望匹配的文本,其中部分内容可以不出现,也可以出现一次或重复出现多次,则可以使用 * 。
import re
if __name__ == '__main__':
regex = re.compile(r'B(an)*')
mo1 = regex.search('B')
print(mo1.group())
mo2 = regex.search('Ban')
print(mo2.group())
mo3 = regex.search('Banana')
print(mo3.group())
最终输出为
B Ban Banan |
7
用加号匹配一次或多次出现的文本
如果你希望匹配的文本,其中部分内容一定要至少出现一次,则可以使用 + 。
import re
if __name__ == '__main__':
regex = re.compile(r'B(an)+')
mo1 = regex.search('B')
print(mo1 is None)
mo2 = regex.search('Ban')
print(mo2.group())
mo3 = regex.search('Banana')
print(mo3.group())
最终输出为
True Ban Banan |
8
用大括号匹配特定次数
如果你希望匹配的文本,其中部分内容一定要出现特定的次数,则可以使用 {} 。
import re
if __name__ == '__main__':
regex = re.compile(r'B(an){1}')
mo1 = regex.search('B')
print(mo1 is None)
mo2 = regex.search('Ban')
print(mo2.group())
mo3 = regex.search('Banana')
print(mo3.group())
最终输出为
True Ban Ban |
当然,我们也可以在 {} 中指定这部分内容出现次数的范围。例如要匹配 'Cz' 至少出现2次,至多出现5次,正则表达式写为 (Cz){2,5} ,则字符串 'CzCz' 'CzCzCz' 'CzCzCzCz' 'CzCzCzCzCz' 都符合匹配规则。
import re
if __name__ == '__main__':
regex = re.compile(r'(Cz){2,5}')
mo1 = regex.search('Cz')
print(mo1 is None)
mo2 = regex.search('CzCz')
print(mo2.group())
mo3 = regex.search('CzCzCzCzCz')
print(mo3.group())
mo4 = regex.search('CzCzCzCzCzCz')
print(mo4.group())
最终输出为
True CzCz CzCzCzCzCz CzCzCzCzCz |
9
查找所有匹配的文本
如果你希望获得所有匹配的文本,可以使用 Regex 对象的 findall() 方法。如果在正则表达式中没有分组,该方法将会返回一个字符串列表;如果在正则表达式中有分组,该方法会返回一个字符串的元组的列表,每个分组对应一个字符串。
import re
if __name__ == '__main__':
regex = re.compile(r'\d\d\d-\d\d\d\d\d\d\d')
mo1 = regex.findall('Tom: 020-1122334 Mary: 010-2233445')
print(mo1)
regex = re.compile(r'(\d\d\d)-(\d\d\d\d\d\d\d)')
mo2 = regex.findall('Tom: 020-1122334 Mary: 010-2233445')
print(mo2)
最终输出为
['020-1122334', '010-2233445'] [('020', '1122334'), ('010', '2233445')] |
10
不区分大小写的匹配
如果你希望在匹配时不区分大小写,可以在 compile() 方法传入第二个参数 re.IGNORECASE 或 re.I。
import re
if __name__ == '__main__':
regex = re.compile(r'python', re.IGNORECASE)
mo1 = regex.search('PYTHON')
print(mo1.group())
最终输出为
PYTHON |
11
替换字符串
如果你希望把匹配的字符串进行替换,可以使用 sub() 方法,第一个参数为要替换成的字符串,第二个参数为一个字符串。
import re
if __name__ == '__main__':
regex = re.compile(r'Mary')
print(regex.sub('Tom', 'Mary is my friend. Mary is 20 years old.'))
最终输出为
Tom is my friend. Tom is 20 years old. |
12
正则表达式模式语法中的特殊元素
符号 | 说明 |
. | 匹配任意字符 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
[] | 匹配来自字符集的任意单一字符 |
[^] | 匹配不在字符集中的任意单一字符 |
* | 匹配0次或多次 |
+ | 匹配1次或多次 |
? | 匹配0次或1次 |
{N} | 匹配N次 |
{M,} | 匹配至少M次 |
{M,N} | 匹配至少M次至多N次 |
A|B | 匹配A或B |
(exp) | 匹配括号内的表达式,也表示一个组 |
(?:exp) | 匹配exp但是不捕获匹配的文本 |
(?#) | 注释 |
(?=exp) | 匹配exp前面的位置 |
(?!exp) | 匹配后面不是exp的位置 |
(?>exp) | 匹配的独立模式,省去回溯 |
(?<=exp) | 匹配exp后面的位置 |
(?<!exp) | 匹配前面不是exp的位置 |
\w | 匹配字母/数字/下划线 |
\W | 匹配非字母/数字/下划线 |
\s | 匹配空白字符(包括\r、\n、\t等) |
\S | 匹配非空白字符 |
\d | 匹配数字 |
\D | 匹配非数字 |
\A | 匹配字符串开始 |
\Z | 匹配字符串结束,只匹配到换行前的结束字符串 |
\z | 匹配字符串结束 |
\G | 匹配最后匹配完成的位置 |
\b | 匹配单词的边界 |
\B | 匹配非单词边界 |
\n,\t等 | 匹配一个换行符,匹配一个制表符等 |
\1...\9 | 匹配第n个分组的内容 |
\10 | 匹配第n个分组的内容,如果它已经匹配,否则指的是八进制字符码的表达式 |
(?<name>exp) | 匹配exp并捕获到名为name的组中 |
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{M,N}? | 重复M到N次,但尽可能少重复 |
{M,}? | 重复M次以上,但尽可能少重复 |
更多内容请关注