Python中的正则表达式(一)
2020-05-08
作者:老齐
与本文相关的图书推荐:《跟老齐学Python:轻松入门》
正则表达式(regular expression)在编程中占有重要地位,它能够按照指定的方式匹配具有某种结构的字符串。本文将对此技术给予详述。
Python中的正则表达式
假设有一个字符串s
,在程序中,需要检查'123'
是否为这个字符串的一部分,这种需求可以用下面的代码实现。
1 | >>> s = 'foo123bar' |
如果你不仅想知道'123'
是否在字符串s
中,还想知道它在字符串的什么位置,可以使用字符串的.find()
或者.index()
方法,返回'123'
在s
中的索引。
1 | >>> s = 'foo123bar' |
在上面的例子中,是按照字符一一对应的方式匹配的,这种方式会适用于很多地方,但是,有时候也会有更复杂的问题。比如,判断字符串中是否有像'123'
这样有数字组成的字符串,例如'foo123bar'
、’foo456bar’、
‘234baz’、和
‘qux678’`,再用上面的方法,就会太麻烦了,这就要正则表达式出场了。
正则表达式极简史
1951年,数学家斯Stephen Cole Kleene提出了正则表达式,20世纪60年代中期,计算机科学先驱Ken Thompson——Unix的原始设计师之一,他使用Kleene创造的符号在QED文本编辑器中实现了模式匹配。
自那时以来,正则表达式就出现在了许多编程语言、编辑器和其他工具中,作为确定字符串是否与指定模式匹配的方法,Python、Java 和 Perl等 都支持正则表达式,大多数Unix工具和许多文本编辑器都支持正则表达式。
re
模块
Python中的正则表达式用re
模块实现,它包括很多实用的方法,接下来,会介绍其中的大部分。
现在,先来研究re.search()
。
1 | re.search(<regex>, <string>) |
re.search(<regex>, <string>)
按照<regex>
参数所设置的正则表达式,扫描<string>
参数的字符串,这个过程可以称为“匹配”,如果有符合正则表达式结构的子字符串,即匹配存在,就会返回第一个所匹配的对象,否则返回None
。
后面还会介绍,re.search()
中的第三个参数<flags>
。
怎么使用re.search()
基本使用方法如下:
1 | import re |
熟悉模块使用方法的,可能会知道,还能这样做:
1 | from re import search |
示例
下面的示例,演示re.search()
的基本应用:
1 | >>> s = 'foo123bar' |
在上面示例中,<regex>
就是123
,<string>
是字符串s
,最后返回了匹配结果,有此结果,我们至少知道字符串s
中含有'123'
。
如果写一个比较完整的程序,可以用条件语句判断一下:
1 | >>> if re.search('123', s): |
前面代码中返回结果<_sre.SRE_Match object; span=(3, 6), match='123'>
,其中的span(3, 6)
意思是字符串中匹配<regex>
出现的位置,与切片的含义一样。
1 | >>> s[3:6] |
不过,上面的例子显然没有体现正则表达式的优势,只说明了它的基本操作流程。
正则表达式中的元字符
在正则表达式中,有一些特定字符,它们被称为元字符,每个元字符,代表了正则表达式中的某个特殊含义,能够满足正则匹配搜索引擎的查询之需。
例如,一组方括号([ ]
)表示了一个元字符类,即匹配字符类中的任何一个字符:
1 | >>> s = 'foo123bar' |
[0-9]
表示要匹配0
到9
的任何一个数字字符,[0-9][0-9][0-9]
则表示匹配连续三个0
到9
之间的任何数字字符,在字符串s
中,符合要求的就是123
。
1 | >>> re.search('[0-9][0-9][0-9]', 'foo456bar') |
如果没有连续的三个数字字符,就不会匹配。
1 | >>> print(re.search('[0-9][0-9][0-9]', '12foo34')) |
另外一个元字符的例子是“句点”(.
),它表示任何一种类型的字符(除了换行符):
1 | >>> s = 'foo123bar' |
在第一个例子中,1
和3
是明显有匹配的,.
匹配了2
。但是,第二个示例则没有匹配。
re
模块的元字符
下表中列出的,是re
模块支持的元字符。
字符 | 说明 |
---|---|
. | 除了换行符之外,匹配任何单个的字符 |
^ | (1)在字符串开头锚定匹配(2)补充字符类 |
$ | 在字符串末尾锚定匹配 |
* | 匹配0个或更多 |
+ | 匹配1个或更多 |
? | 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。 |
{n} | n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。 |
\ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,’n’ 匹配字符 “n”。’\n’ 匹配一个换行符。序列 ‘\‘ 匹配 “" 而 “(“ 则匹配 “(“。 |
[] | 特定字符集 |
() | 创建组 |
: # = ! | 特定组 |
<> | 有名称的组 |
下面以示例说明部分元字符的应用。
字符类符号:[ ]
[ ]
里面表示的字符类,即要匹配的一个字符集合。
1 | >>> re.search('ba[artz]', 'foobarqux') |
[artz]
表示4个独立的字符,在上面的示例中,正则表达式ba[artz]
匹配了字符串中的bar
和baz
,当然,如果有可能,还可以匹配baa
、bat
。
字符集中,可以用-
表示字符序列的范围,例如[a-z]
表示匹配英文小写字母a
到z
中的任何一个字母。
1 | >>> re.search('[a-z]', 'FOObar') |
[0-9]
表示任何一个数字字符:
1 | >>> re.search('[0-9][0-9]', 'foo123bar') |
在这个示例中,[0-9][0-9]
表示匹配两个数字组成的字符串,对于字符串foo123bar
,匹配了第一次出现的符合正则表达式的部分。
[0-9a-fA-F]
表示16进制的任何一个字符。
1 | >>> re.search('[0-9a-fA-f]', '--- a0 ---') |
这里匹配了第一个出现的16进制的字符a
。
也可以在字符类中以^
作为第一个字符,则表示要匹配该字符类的补集,即所有不是字符集中的字符。如下所示,[^0-9]
表示非数字字符。
1 | >>> re.search('[^0-9]', '12345foo') |
这里匹配的结果是第一个非数字的字母字符f
。如果^
不在首位,就没有了上面那种特殊含义了,仅仅是一个普通的^
符号。
1 | >>> re.search('[#:^]', 'foo^bar:baz#qux') |
前面已经说过,-
表示了字符范围,但是,如果希望在正则表达式中匹配一个连字符-
,怎么办?如果-
在首位或者末尾,就表示连字符本身,如果在中间,可以只用转义符\
。
1 | >>> re.search('[-abc]', '123-456') |
同样,对于]
符号,也可以用类似方法处理。
1 | >>> re.search('[]]', 'foo[1]') |
在[ ]
所设定的字符集中,其他各种元字符都失掉了作为元字符的含义。
1 | >>> re.search('[)*+|]', '123*456') |
这些元字符都编程了普通的字符。
(未完,待续)
参考资料:https://realpython.com/regex-python/
搜索技术问答的公众号:老齐教室
在公众号中回复:老齐,可查看所有文章、书籍、课程。
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
关注微信公众号,读文章、听课程,提升技能