核心编程之第一章正则表达式

1.当严格讨论与字符串中模式相关的正则表达式 时,我们会用术语“匹配”(matching),指的是术语“模式匹配”(pattern-matching)。在 Python 术语中,主要有两种方法完成模式匹配:“搜索”(searching),即在字符串任意部 分中搜索匹配的模式;而“匹配”(matching)是指判断一个字符串能否从起始处全部或者 部分地匹配某个模式。搜索通过 search()函数或方法来实现,而匹配通过调用 match()函数 或方法实现。总之,当涉及模式时,全部使用术语“匹配”;我们按照 Python 如何完成模 式匹配的方式来区分“搜索”和“匹配”。

2.正则表达式的强大之处在于引入特殊字符来定义字 符集、匹配子组和重复模式。正是由于这些特殊符号,使得正则表达式可以匹配字符串集合, 而不仅仅只是某单个字符串。

3.特殊符号和字符

表1-1 常见正则表达式符号和特殊字符
表示法 描述 正则表达式示例
   
literal 匹配文本字符串的字面值literal foo
re1|re2 匹配正则表达式re1或者re2 foo|bar

.

匹配任何字符(除了\n之外) b.b
^ 匹配字符串起始部分 ^Dear
$ 匹配字符串终止部分 /bin/*sh$
* 匹配0次或者多次前面出现的正则表达式 [A-Za-z0-9]*
+ 匹配1次或者多次前面出现的正则表达式 [a-z]+\.com
? 匹配0次或者1次前面出现的正则表达式 goo?
{N} 匹配N次前面出现的正则表达式 [0-9]{3}
{M,N} 匹配M~N次前面出现的正则表达式 [0-9]{5,9}
[...] 匹配来自字符集的任意单一字符 [aeiou]
[.x-y..] 匹配x~y范围中的任意单一字符 [0-9],[A-Za-z]
[^...] 不匹配此字符集中出现的任何一个字符,包括某一范围的字符(如果在此字符集中出现) [^aeiou],[^A-Za-z0-9]
(*|+|?|{})? 用于匹配上面频繁出现/重复出现符号的非贪婪版本(*、+、?、{}) .*?[a-z]
(...) 匹配封闭的正则表达式,然后另存为子组 ([0-9]{3})?,f(oo|u)bar
特殊字符  
\d 匹配任何十进制数字,与[0-9]一致(\D与\d相反,不匹配任何非数值型的数字) data\d+.txt
\w 匹配任何字母数字字符,与[A-Za-z0-9]相同(\W与之相反) [A-Za-z]\w+
\s 匹配任何空格字符,与[\n\t\r\v\f]相同(\S与之相反) of\sthe
\b 匹配任何单词边界(\B与之相反) \bThe\b
\N 匹配已保存的子组N(参见上面的(...)) price:\16
\c 逐字匹配任何特殊字符c(即,仅按照字面意义匹配,不匹配特殊含义) \.,\\,\*
\A(\Z) 匹配字符串的起始(结束)(另见上面介绍的^和$) \ADear
扩展表示法  
(?iLmsux) 在正则表达式中嵌入一个或者多个特殊'标记'参数(或者通过函数、方法) (?x),(?im)
(?...) 表示一个匹配不用保存的分组 (?:\w+\.)*
(?P<name>...) 像一个仅由name标识而不是数字ID标识的正则分组匹配 (?P<data>)
(?P<name>) 在同一字符串中匹配由(?P<name>)分组的之前文本 (?P=data)
(?#...) 表示注释,所有内容都被忽略 (?#comment)
(?=...) 匹配条件是如果...出现在之后的位置,而不使用输入字符串:称作正向前视断言 (?=.com)
(?!...) 匹配条件是如果...不出现在之后的位置,而不使用输入字符串:称作负向前视断言 (?!.net)
(?<=...) 匹配条件是如果...出现在之前的位置,而不使用输入字符串:称作正向后视断言 (<=800-)
(?<!...) 匹配条件是如果...不出现在之前的位置,而不使用输入字符串:称作负向后视断言 (?<!192\.168\.)
表示(?(id/name)Y|N) 如果分组所提供的id或者name(名称)存在,就返回正则表达式的条件匹配Y,如果不存在,就返回N;|N是可选项 (?(1)y|x)
  • 使用择一匹配符号匹配多个正则表达式模式

  表示择一匹配的管道符号(|),也就是键盘上的竖线,表示一个“从多个模式中选择其 一”的操作。它用于分割不同的正则表达式。

正则表达式模式 匹配在字符串
at|home at、home
james|jordan james、jordan
bat|bet|bit bat、bet、bit
    • 匹配任意单个字符

   点号或者句点(.)符号匹配除了换行符\n 以外的任何字符(Python 正则表达式有一个编,该标记能够推翻这个限制,使点号能够匹配换行符)。无论字母、 数字、空格(并不包括“\n”换行符)、可打印字符、不可打印字符,还是一个符号,使用点 号都能够匹配它们。

正则表达式模式 匹配的字符串
f.o 匹配在字母f和o之间的任意一个字符:例如fao、f9o、f#o等
.. 任意俩个字符
.end 匹配在字符串end之前的任意一个字符

问:怎样才能匹配句点(dot)或者句号(period)字符?

答:要显式匹配一个句点符号本身,必须使用反斜线转义句点符号的功能,例如“\.”。

  • 从字符串起始或者结尾或者单词边界匹配

  还有些符号和相关的特殊字符用于在字符串的起始和结尾部分指定用于搜索的模式。如 果要匹配字符串的开始位置,就必须使用脱字符(^)或者特殊字符\A(反斜线和大写字母 A)。 后者主要用于那些没有脱字符的键盘(例如,某些国际键盘)。同样,美元符号($)或者\Z 将用于匹配字符串的末尾位置。 使用这些符号的模式与本章描述的其他大多数模式是不同的,因为这些模式指定了位置 或方位。之前的“核心提示”记录了匹配(试图在字符串的开始位置进行匹配)和搜索(试 图从字符串的任何位置开始匹配)之间的差别。正因如此,下面是一些表示“边界绑定”的 正则表达式搜索模式的示例。

正则表达式模式 匹配的字符串
^From 任何以From作为起始的字符串
/bin/tcsh$ 任何以/bin/tcsh作为结尾的字符串
^Subject:hi$ 任何由单独的字符串Subject:hi构成的字符串

  再次说明,如果想要逐字匹配这些字符中的任何一个(或者全部),就必须使用反斜线进行转义。例如,如果你想要匹配任何以美元符号结尾的字符串,一个可行的正则表达式方案 就是使用模式.*\$$。 特殊字符\b 和\B 可以用来匹配字符边界。而两者的区别在于\b 将用于匹配一个单词的边 界,这意味着如果一个模式必须位于单词的起始部分,就不管该单词前面(单词位于字符串 中间)是否有任何字符(单词位于行首)。同样,\B 将匹配出现在一个单词中间的模式(即, 不是单词边界)。下面为一些示例。

    • 创建字符集

  尽管句点可以用于匹配任意符号,但某些时候,可能想要匹配某些特定字符。正因如此, 发明了方括号。该正则表达式能够匹配一对方括号中包含的任何字符。下面为一些示例。

正则表达式模式 匹配的字符串
b[aeiu]t bat、bet、bit、but
[cr][23][dp][o2] 一个包含四个字符的字符串,第一个字符是“c”或“r”,然后是“2”或“3”,后面 是“d”或“p”,最后要么是“o”要么是“2”。例如,c2do、r3p2、r2d2、c3po等

  关于[cr][23][dp][o2]这个正则表达式有一点需要说明:如果仅允许 “r2d2”或者“c3po” 作为有效字符串,就需要更严格限定的正则表达式。因为方括号仅仅表示逻辑或的功能, 所以使用方括号并不能实现这一限定要求。唯一的方案就是使用择一匹配,例如, r2d2|c3po。 然而,对于单个字符的正则表达式,使用择一匹配和字符集是等效的。例如,我们以正 则表达式“ab”作为开始,该正则表达式只匹配包含字母“a”且后面跟着字母“b”的字符 串,如果我们想要匹配一个字母的字符串,例如,要么匹配“a”,要么匹配“b”,就可以使 用正则表达式[ab],因为此时字母“a”和字母“b”是相互独立的字符串。我们也可以选择 正则表达式 a|b。然而,如果我们想要匹配满足模式“ab”后面且跟着“cd”的字符串,我们 就不能使用方括号,因为字符集的方法只适用于单字符的情况。这种情况下,唯一的方法就 是使用 ab|cd,这与刚才提到的 r2d2/c3po 问题是相同的。

    • 限定范围和否定

除了单字符以外,字符集还支持匹配指定的字符范围。方括号中两个符号中间用连字符 (-)连接,用于指定一个字符的范围;例如,A-Z、a-z 或者 0-9 分别用于表示大写字母、小 写字母和数值数字。这是一个按照字母顺序的范围,所以不能将它们仅仅限定用于字母和十 进制数字上。另外,如果脱字符(^)紧跟在左方括号后面,这个符号就表示不匹配给定字符 集中的任何一个字符。

正则表达式模式 匹配的字符串
z.[0-9] 字母“z”后面跟着任何一个字符,然后跟着一个数字
[r-u][env-y][us] 字母“r”、“ s”、“ t”或者“u”后面跟着“e”、“ n”、“ v”、“ w”、“ x”或者“y”,然后跟着“u”或者“s”
[^aeiou] 一个非元音字符(练习:为什么我们说“非元音”而不是“辅音”?)
[^\t\n] 不匹配制表符或者\n
[“-a] 在一个 ASCII 系统中,所有字符都位于“”和“a”之间,即 34~97 之间
    • 使用闭包操作符实现存在性和频数匹配

本节介绍最常用的正则表达式符号,即特殊符号*、+和?,所有这些都可以用于匹配一 个、多个或者没有出现的字符串模式。星号或者星号操作符(*)将匹配其左边的正则表达式 出现零次或者多次的情况(在计算机编程语言和编译原理中,该操作称为 Kleene 闭包)。加 号(+)操作符将匹配一次或者多次出现的正则表达式(也叫做正闭包操作符),问号(?) 操作符将匹配零次或者一次出现的正则表达式。 还有大括号操作符({}),里面 或者是单个值或者是一对由逗号分隔的值。这将最终精 确地匹配前面的正则表达式 N 次(如果是{N})或者一定范围的次数;例如,{M
, N}将匹 配 M~N 次出现。这些符号能够由反斜线符号转义;\*匹配星号,等等。 注意,在之前的表格中曾经多次使用问号(重载),这意味着要么匹配 0 次,要么匹配 1 次,或者其他含义:如果问号紧跟在任何使用闭合操作符的匹配后面,它将直接要求正则表 达式引擎匹配尽可能少的次数。 “尽可能少的次数”是什么意思?当模式匹配使用分组操作符时,正则表达式引擎将试图 “吸收”匹配该模式的尽可能多的字符。这通常被叫做贪婪匹配。问号要求正则表达式引擎去 “偷懒”,如果可能,就在当前的正则表达式中尽可能少地匹配字符,留下尽可能多的字符给 后面的模式(如果存在)。本章末尾将用一个典型的示例来说明非贪婪匹配是很有必要的。现 在继续查看闭包操作符。

正则表达式模式 匹配的字符串
[dn]ot? 字母“d”或者“n”,后面跟着一个“o”,然后是最多一个“t”,例如,do、no、dot、not
0?[1-9] 任何数值数字,它可能前置一个“0”,例如,匹配一系列数(表示从 1~9 月的数值),不 管是一个还是两个数字 [
[0-9]{15,16} 匹配 15 或者 16 个数字(例如信用卡号码)
</?[^>]+> 匹配全部有效的(和无效的)HTML 标签
[KQRBNP][a-h][1-8]-[a-h][1-8] 在“长代数”标记法中,表示国际象棋合法的棋盘移动(仅移动,不包括吃子和将军)。 即“K”、“ Q”、“ R”、“ B”、“ N”或“P”等字母后面加上“a1”~“h8”之间的棋盘坐标。 前面的坐标表示从哪里开始走棋,后面的坐标代表走到哪个位置(棋格)上
    • 表示字符集的特殊字符

我们还提到有一些特殊字符能够表示字符集。与使用“0-9”这个范围表示十进制数相比, 可以简单地使用 d 表示匹配任何十进制数字。另一个特殊字符(\w)能够用于表示全部字母 数字的字符集,相当于[A-Za-z0-9_]的缩写形式,\s 可以用来表示空格字符。这些特殊字符的 大写版本表示不匹配;例如,\D 表示任何非十进制数(与[^0-9]相同),等等。 使用这些缩写,可以表示如下一些更复杂的示例。

正则表达式模式 匹配的字符串
\w+-\d+ 一个由字母数字组成的字符串和一串由一个连字符分隔的数字
[A-Za-z]\w* 第一个字符是字母;其余字符(如果存在)可以是字母或者数字(几乎等价于 Python 中的有 效标识符)
\d{3}-\d{3}-\d{4} 美国电话号码的格式,前面是区号前缀,例如 800-555-1212
\w+@\w+\.com 以 XXX@YYY.com 格式表示的简单电子邮件地址
    • 使用圆括号指定分组

   现在,我们已经可以实现匹配某个字符串以及丢弃不匹配的字符串,但有些时候,我们 可能会对之前匹配成功的数据更感兴趣。我们不仅想要知道整个字符串是否匹配我们的标准, 而且想要知道能否提取任何已经成功匹配的特定字符串或者子字符串。答案是可以,要实现 这个目标,只要用一对圆括号包裹任何正则表达式。

   当使用正则表达式时,一对圆括号可以实现以下任意一个(或者两个)功能:

   • 对正则表达式进行分组;

   • 匹配子组。

   关于为何想要对正则表达式进行分组的一个很好的示例是:当有两个不同的正则表达式 而且想用它们来比较同一个字符串时。另一个原因是对正则表达式进行分组可以在整个正则 表达式中使用重复操作符(而不是一个单独的字符或者字符集)。

   使用圆括号进行分组的一个副作用就是,匹配模式的子字符串可以保存起来供后续使用。 这些子组能够被同一次的匹配或者搜索重复调用,或者提取出来用于后续处理。1.3.9 节的结 尾将给出一些提取子组的示例。

   为什么匹配子组这么重要呢?主要原因是在很多时候除了进行匹配操作以外,我们还想 要提取所匹配的模式。例如,如果决定匹配模式\w+-\d+,但是想要分别保存第一部分的字母 和第二部分的数字,该如何实现?我们可能想要这样做的原因是,对于任何成功的匹配,我 们可能想要看到这些匹配正则表达式模式的字符串究竟是什么。

如果为两个子模式都加上圆括号,例如(\w+)-(\d+),然后就能够分别访问每一个匹配 子组。我们更倾向于使用子组,这是因为择一匹配通过编写代码来判断是否匹配,然后执行另一个单独的程序(该程序也需要另行创建)来解析整个匹配仅仅用于提取两个部分。为什么不让 Python 自己实现呢?这是 re 模块支持的一个特性,所以为什么非要重蹈覆辙呢?

正则表达式模式 匹配的字符串
\d+(\.\d*)? 表示简单浮点数的字符串;也就是说,任何十进制数字,后面可以接一个小数点和零个或 者多个十进制数字,例如“0.004”、“ 2”、“ 75.”等
(Mr?s?\.)?[A-Z][a-z]*[A-Za-z-]+ 名字和姓氏,以及对名字的限制(如果有,首字母必须大写,后续字母小写),全名前可以 有可选的“Mr.”、“ Mrs.”、“ Ms.”或者“M.”作为称谓,以及灵活可选的姓氏,可以有多 个单词、横线以及大写字母
 
    • 扩展表示法

我们还没介绍过的正则表达式的最后一个方面是扩展表示法,它们是以问号开始(?…) 。 我们不会为此花费太多时间,因为它们通常用于在判断匹配之前提供标记,实现一个前视(或 者后视)匹配,或者条件检查。尽管圆括号使用这些符号,但是只有(?P<name>)表述一个 分组匹配。所有其他的都没有创建一个分组。然而,你仍然需要知道它们是什么,因为它们 可能最适合用于你所需要完成的任务。

正则表达式模式 匹配的字符串
(?:\w+\.)* 以句点作为结尾的字符串,例如“google.”、“ twitter.”、“ facebook.”,但是这些匹配不会保存下来 供后续的使用和数据检索
(?#comment) 此处并不做匹配,只是作为注释
(?=.com) 如果一个字符串后面跟着“.com”才做匹配操作,并不使用任何目标字符串
(?!.net) 如果一个字符串后面不是跟着“.net”才做匹配操作
(?<=800-) 如果字符串之前为“800-”才做匹配,假定为电话号码,同样,并不使用任何输入字符串
(?<!192\.168\.) 如果一个字符串之前不是“192.168.”才做匹配操作,假定用于过滤掉一组 C 类 IP 地址
(?(1)y|x) 如果一个匹配组 1(\1)存在,就与 y匹配;否则,就与 x 匹配

  

posted @ 2018-08-14 20:55  文濤  阅读(388)  评论(0编辑  收藏  举报