Python1、预备知识2、正则功能详解2.1、列表2.2、字符组2.2.1、Python 22.2.2、Python 32.2.3、关于编码2.3、Unicode属性2.4、字符组简记法2.4.1、Python 22.4.2、Python 32.5、单词边界2.5.1、Python 22.5.2、Python 32.6、行起始/结束位置2.7、环视2.8、匹配模式2.9、捕获分组的引用2.10、条件匹配3、正则API简介3.1、RegexObject3.2、re.compile(regex[,flags])3.3、re.search(pattern,string[,flags])3.4、MatchObject3.5、re.match(pattern,string[,flags])3.6、re.findall(pattern,string[,flags])3.7、re.finditer(pattern,string[,flags])3.8、re.split(pattern,string[,maxsplit0,flags0])3.9、re.sub(pattern,repl,string[,count,flags])4、常用操作示例4.1、验证4.2、提取4.3、替换4.4、切分Python对正则表达式的支持较为完备它的API很丰富也支持Unicode字符串Python 2的字符串并没有默认使用Unicode编码所以使用上稍微麻烦一点但不支持Unicode属性。因为Python 2和Python 3在正则表达式的规定上大不相同本文的讲解主要基于Python 2具体版本是Python 2.7.14示例代码通常也可以用于Python 3基于Python 3.6.4进行了全部测试遇到区别会专门讲解。1、预备知识Python内置了正则表达式的package在使用正则表达式之前必须首先导入对应的package为讲解方便先介绍Python中的re.search(pattern,string)函数。如果pattern可以匹配string的某个子串则返回一个MatchObject对象否则返回None。通过判断是否返回None就可以知道pattern能否匹配string中的子串。如果在正则表达式两端加上\A和\Z注意是\Z而不是\zPython中没有\z\Z等价于其他语言中的\z则可以判断整个字符串是否能由正则表达式匹配进行正则表达式验证。Python的字符串比较复杂值得花点篇幅进行介绍Python中常用的字符串有单引号字符串和双引号字符串两者并没有区别\d{6}与’\d{6}是完全等价的。如果使用这两种字符串来表示正则表达式都必须经过字符串转义与正则转义也就是说正则表达式\d应当表现为字符串文字\d。但是Python也提供了另一种“原生字符串RawString”其方式是在字符串之前添加字符r这样就会忽略字符串转义只需要提供正则文字即可。这样正则表达式\d就可以写作r\d{6}或r’\d’。这种形式更适合正则表达式所以本章中的正则表达式都使用这种形式。你有可能在某些代码中看到正则表达式\d直接使用字符串文字\d而没有使用原生字符串但也并没有出错。这是因为Python在处理字符串转义时如果字符串文字中的某个转义序列无法识别则会被原封不动地“保留下来”。\d并不是可以识别的字符串转义序列所以会被保留下来作为正则文字。因此\d也可以直接写成字符串\d同样\A和\Z也是如此。但我并不推荐使用这种做法因为表达式\b不能写成\b—因为在字符串文字中\b是可以识别的字符串转义序列表示退格符backspace所以\bcat\b生成的正则表达式不能匹配“单词cat”而是匹配“两个退格符之间的cat”。另外Python 2的字符串并非默认采用Unicode编码所以如果要用到Unicode功能并非Unicode属性[2]则必须在字符串之前添加字符u比如u[你我他]“。所以如果字符串中出现了多字节字符最好使用u属性写成u你”如果正则表达式中出现了多字节字符最好是同时使用u和r。不过注意只能写作ur[你我他]“,不能写作ru”[你我他]“。而在Python 3中因为字符串默认采用Unicode编码所以只需用r标记比如r”[你我他]。2、正则功能详解2.1、列表下表列出了Python中的正则功能。2.2、字符组2.2.1、Python 2除非显式指定否则Python 2的字符串默认都采用ASCII编码正则表达式也无法正确识别GBK编码环境下的多字节字符因此可能遇到错误匹配的问题。解决的办法是使用Unicode编码环节。请注意不管是用作正则表达式的字符串还是被匹配的字符串只要出现了中文字符都必须指定u。2.2.2、Python 3Python 3中的字符串默认采用Unicode编码所以不存在上面的问题。2.2.3、关于编码如果不清楚自己使用的Python采用什么编码可以运行下面的代码得到。如果显示ASCII则会遇到GBK编码的错误如果显示为UTF-8则不会。有时Python可能会提示程序文件中出现的UTF-8字符无法识别这时应该在.py文件的开头加上这样一行告诉解释器本源文件使用的是UTF-8编码在Mac OS和Linux/UNIX下Python会自动从环境变量中获得编码通过设定LANGen_US.UTF-8或LANGzh_CN.UTF-8。Windows默认使用的并不是Unicode编码。所以如果要在Windows下使用Python又要使用UTF-8编码除了直接使用Python 3Python 2必须在.py文件的顶部加上这一行。在Python 2下还有一个推荐的做法一次性将所有字符串都指定为Unicode字符串这样确实省事但仍然可能遇到一些问题。因为Python2的不少函数只接受str类型的字符串不接受Unicode字符串。解决办法如下面的代码所示2.3、Unicode属性Python 2和Python 3都不支持Unicode属性也就是说所有规定的Unicode属性都无法在Python中使用。如果一定要用Unicode属性可以使用第三方库比如Ponyguruma。具体请读者自行搜索此处不做详细介绍。2.4、字符组简记法2.4.1、Python 2在Python 2中在默认情况下\d、\D、\w、\W、\s、\S都采用字符组简记法的ASCII匹配规则☞121。也就是说\d等价于[0-9]\w等价于[0-9a-zA-Z_]\s无法匹配其他空白字符比如中文的全角空格。但是如果启用Unicode匹配模式最简单的做法是在表达式的最前面加上(?u)则会启用字符组简记法的Unicode匹配规则。目前已经出现了两个概念“Unicode字符串”和“Unicode模式”它们比较容易混淆表14-2有助于澄清这两个概念。2.4.2、Python 3在Python 3中各字符组简记法默认都采用字符组简记法的Unicode匹配规则。也就是说在默认情况下\d能匹配Unicode的数字字符\w能匹配Unicode的单词字符\s匹配Unicode的空白字符。如果要“还原”为ASCII匹配规则则必须显式设定ASCII模式最简单的做法是在表达式最前面加上(?a)。在Python 2和Python 3中默认字符组简记法的匹配规则完全相反使用时必须尤其注意。比如在Python 2中完全可以用^\d$验证ASCII字符数字的字符串但在Python 3中则不能这样做。2.5、单词边界2.5.1、Python 2Python 2的正则表达式中单词边界的规定与对\w的规定是一样的。也就是说\b匹配成功只有一种情况一边必须保证\w等价于[0-9a-zA-Z_]匹配成功另一边必须保证\w匹配不成功。如果显式指定了Unicode匹配模式则\w能匹配的字符包括中文字符所以\b的匹配也相应变化。2.5.2、Python 3因为Python 3默认采用Unicode匹配规则默认情况下\w认定的单词字符就包括其他语言中的字符包括中文字符等价于Python 2显式指定Unicode模式的情况。如果希望使用ASCII匹配规则则应当显式指定ASCII模式它等价于Python 2中的默认情况。2.6、行起始/结束位置^匹配的是“行开始的位置”在默认情况下它只能匹配“整个字符串的开始位置”如果指定使用多行模式Multiline Mode^可以匹配字符串内部的文本行的开始位置而\A无论在什么情况下都匹配“整个字符串的开始位置”。在默认情况下Python中的和 Z 匹配的是整个字符串的结束位置如果结束位置有换行符则 和\Z匹配的是整个字符串的结束位置如果结束位置有换行符则和Z匹配的是整个字符串的结束位置如果结束位置有换行符则匹配这个换行符之前的位置使用多行模式会影响$的匹配这时候它可以匹配文本内部行的结束位置。所以如果要进行严格准确的验证应当使用\Z它等价于其他语言中的\z。2.7、环视Python中的肯定顺序环视(?…)和否定顺序环视(?!…)内可以使用任意形式的子表达式。Python中的逆序环视则多了很多限制在逆序环视(?…)和(?!…)中表达式能匹配的文本长度必须是固定的否则就会报错。也就是说逆序环视中的表达式不能使用、*、?之类的量词如果出现多选结构各个多选分支能匹配的文本长度也必须是相同的。2.8、匹配模式Python的正则表达式支持所有常见的模式另外还提供了Unicode模式和ASCII模式详见下表。匹配模式可以在表达式中以(?…)指定也可以将常量作为参数指定。需要注意的是Python中的模式永远是对整个正则表达式生效的无论(?…)出现在哪里。Python不支持用(?-…)停用模式。2.9、捕获分组的引用在Python中如果要在正则表达式内部引用捕获分组则应当使用\num记法其中num为对应捕获分组的编号。如果要在替换时引用捕获分组则仍然使用\num记法。更好的办法是使用\g这样更加清晰避免了二义性。如果使用了命名分组在表达式中应当使用(?Pname)来引用在替换时应当使用\gname来引用。2.10、条件匹配Python的正则表达式支持条件匹配这是一个很有趣的功能值得详细介绍一下。在正则表达式的匹配过程中我们有时候会希望进行更细致的操控假如前面这样匹配了后面要这样匹配假如前面那样匹配了后面要那样匹配。但是经典的正则表达式语法并没有提供这样的功能往往只能靠多个正则表达式加上程序代码来实现。Python的正则引擎直接支持条件匹配使用起来就方便很多了。条件匹配的语法是这样的(?(id/name)yes-pattern|no-pattern)。其中id/name是对应捕获分组的名称或者编号如果该捕获分组成功匹配文本则后续匹配交由yes-pattern来完成否则交由no-pattern来完成。no-pattern不是必须出现的可以省略。假设我们需要验证价格规则如下如果前面没有美元符号$则价格只能包含整数部分否则还应当包含小数点和两位小数。条件匹配正适合解决这个问题。这个例子看起来有些简单确实通常我们都会在正则表达式中使用多选分支也就是^($[0-9].[0-9]{2}|[0-9])$可起到同样的效果。但是如果两个多选分支中相同的部分是一个复杂的表达式而不是本例中的[0-9]那么条件匹配会让表达式简单很多。3、正则API简介3.1、RegexObjectPython中的正则表达式对象是RegexObject它可以由正则表达式编译而来常用的方法是re.compile(pattern)。如果要指定匹配模式则可以在表达式中使用模式修饰符也可以使用预定义常量。如果要同时指定多个匹配模式则可以在表达式中连写模式修饰符也可以用|运算符连接预定义常量。在Python的文档中大多数函数是类似re.search(pattern,string)的形式其中的pattern通常是正则表达式的字符串可以将其视为“静态方法”。其实也可以将RegexObject对象作为pattern参数传入或者直接对RegexObject对象调用相同名字的成员方法结果相同但是这两种做法能显式缓存RegexObject对象。下面的代码分别使用了三种办法结果相同。re.compile()还有一个不为许多人所知的功能就是它可以用来观察某个正则表达式的详细信息方法是指定第二个参数为re.DEBUG。如果你遇到复杂的表达式或者不确定某个表达式的意义可以通过它来观察。下面给出两个简单的例子其中的缩进表示了表达式各结构的层级关系而字符本身则显示为其码值的十进制表示比如字符a的码值是十进制的97。3.2、re.compile(regex[,flags])这个方法用于显式“编译”正则表达式对象。如果需要多次使用同一个正则表达式那么每次使用编译好的正则表达式对象比每次临时编译表达式要快得多。re.compile()的实际使用在上一节已经看到过这里不再重复。需要指出的是Python会自动缓存最近一次在re.match()、re.search()和re.compile()中指定的正则表达式。如果用到的正则表达式并不多可以使用字符串形式的正则表达式而不必显式调用re.compile()获得RegexObject对象。也正因为如此本章的大部分例子都没有显式调用re.compile()而是直接以字符串方式给出正则表达式。在实际编程中可以根据具体情况决定是否显式调用re.compile()。3.3、re.search(pattern,string[,flags])这个方法用来测试正则表达式pattern能否在string中找到匹配flags是可选参数表示匹配模式下面各个方法中的flags参数都具有同样的功能不再赘述。如果能找到匹配则返回MatchObject详见下文否则返回None。这个函数可以用来进行数据验证在正则表达式两端加上\A和\ZPython不支持\z\Z等价于其他语言中的\z并判断返回值是否为None。3.4、MatchObject调用re.search()或者re.match()如果匹配成功就返回一个MatchObject对象。实际上与正则表达式匹配有关的许多方法都会返回MatchObject对象。MatchObject提供了若干方法和属性通过它们可以获得匹配的信息下表列出了常用的方法和属性。下面的代码集中示范了其中主要的方法。3.5、re.match(pattern,string[,flags])re.match()和re.search()非常相似参数相同返回值也相同非常容易混淆唯一的区别在于如果re.match()匹配成功那么pattern匹配的字符串必须开始于string的最左端。也就是说re.match()只会在字符串的开始位置尝试匹配re.search()则没有这个限制。re.match()的名字容易让人认为这是专门用来进行数据验证的方法。其实单纯调用它并不能准确验证还必须在正则表达式末尾加上\Z不过更好的做法是在正则表达式首尾加上\A和\Z这样更符合惯例。3.6、re.findall(pattern,string[,flags])这个函数会一次性找出正则表达式pattern在string中的所有匹配返回一个列表最简单的用法就是如果pattern中存在捕获分组则返回列表的元素并非整个表达式所匹配的文本而是各个捕获分组所匹配文本构成的元组。请注意如果pattern中存在捕获分组整个表达式匹配的文本不会包含在返回文本中而且返回元组中的元素偏移值与分组编号并不对应编号为1的分组匹配的文本是元组中的元素0编号为2的分组匹配的文本是元组中的元素1依此类推。如果需要得到整个表达式匹配的文本则可以显式给整个表达式增加括号。因为它返回的是列表而不是字典所以即便使用了命名分组re.findall()也只会将它们当作数字编号分组来处理而不会保存任何与命名有关的信息。3.7、re.finditer(pattern,string[,flags])如果文本内容很多使用re.findall()一次性找出所有匹配的成本可能很高使用re.finditer()可以返回一个迭代器iterator它可以按从左到右的顺序逐个遍历每次匹配的MatchObject对象依次进行处理或检验。3.8、re.split(pattern,string[,maxsplit0,flags0])这个函数用正则表达式pattern能够匹配的文本切分字符串string。如果正则表达式在字符串的开头或结尾位置能找到匹配那么结果的开头或结尾会出现空字符串。也可以明确设定maxsplit。请注意在其他语言中这个参数一般表示返回数组的长度但是在Python中它表示切分的次数。也就是说返回数组一般会包含maxsplit1个元素。如果将maxsplit设定为负数则表示“不做任何切分”若设定为0则等于没有指定若设定的值为正数且小于实际可切分次数则按设定的值进行切分若大于实际可切分次数则以实际可切分次数为准。3.9、re.sub(pattern,repl,string[,count,flags])这个函数用来进行正则表达式替换它将字符串string中正则表达式pattern能够匹配的文本替换为repl指定的文本。如果要在replacement字符串中引用之前某个捕获分组匹配的文本则应当使用\num不过因为replacement是字符串如果使用字符串文字则应当写成\num。为了清晰直接推荐使用原生字符串也就是在字符串之前添加r。Python也提供了更清晰的方式在repl中引用分组写作\gnum。这种记法也可以用来引用命名分组写作\gname。repl也可以是一个函数它接收一个MatchObject对象返回一个String对象。这样替换功能就强大了许多比如把所有单词统一为首字母大写格式。也可以设定count参数它指定替换操作最多能发生的次数。4、常用操作示例4.1、验证简单验证复用RegexObject验证4.2、提取简单提取#出现捕获分组之后返回的结果不会包含默认的捕获分组0所以显式加上分组0复用RegexObject对象使用finditer迭代查找使用命名分组4.3、替换简单替换命名分组替换在替换中使用整个表达式匹配的文本4.4、切分