在Python正则表达式中,逻辑“或”用竖线 | 表示,用来匹配多个备选模式中的任意一个。正则表达式没有专门的逻辑与、非符号:顺序书写的字符本身构成“与”,负向先行断言(?!)、字符类中的^等可表达“非”,而|负责“或”。下面基于Python re模块的findall示例,说明|在整体分支、分组、量词组合和字符类中的不同行为。
一、用|分割整个正则的备选分支- import re
- import time
- a = "abc efg hij"
- b = re.findall(r"abc|efg", a)
- c = re.findall(r'abc|efg|hij', a)
- print("b = {}, c = {}".format(b, c))
- # 输出结果:b = ['abc', 'efg'], c = ['abc', 'efg', 'hij']
复制代码 b中|连接abc和efg,只要匹配abc或efg中任意一个子串即可;c增加hij分支后,能匹配abc、efg、hij中的任意一个。这里re.findall(pattern, string)返回所有满足分支条件的子串列表。
二、用分组限制|的作用域- import re
- a = "abc21efg123hij111"
- b = re.findall(r"(?:abc|efg)\d+", a)
- c = re.findall(r'(?:abc|efg|hij)\d+', a)
- print("b = {}, c = {}".format(b, c))
- # 输出结果:b = ['abc21', 'efg123'],
- # c = ['abc21', 'efg123', 'hij111']
复制代码 (?:...)是不捕获分组,findall会返回完整匹配内容。此时|只在分组内生效:b匹配“abc后跟数字”或“efg后跟数字”,c再多一个“hij后跟数字”分支。
如果去掉括号,|的作用域会扩展到整个正则表达式:- import re
- a = "abc21efg123hij111"
- b = re.findall(r"abc|efg\d+", a)
- c = re.findall(r'abc|efg|hij\d+', a)
- print("b = {}, c = {}".format(b, c))
- # 输出结果:b = ['abc', 'efg123'],
- # c = ['abc', 'efg', 'hij111']
复制代码 b匹配的是abc分支,或efg\d+分支,而不是(abc|efg)\d+。这说明没有分组时,|不再只约束前面的abc或efg,而是把整个左右两侧拆成备选分支。结果中只有最后一个分支带了数字,正是作用域变化造成的。
三、|与量词组合时的贪婪和非贪婪- import re
- a = "abcefg123efgabchij123efghij111"
- b = re.findall(r"(?:abc|efg)+", a)
- c = re.findall(r'(?:abc|efg|hij)+', a)
- d = re.findall(r"(?:abc|efg)+?", a)
- e = re.findall(r'(?:abc|efg|hij)+?', a)
- print("b = {}, c = {}, d = {}, e = {}".
- format(b, c, d ,e))
- # 输出结果:b = ['abcefg', 'efgabc', 'efg'],
- # c = ['abcefg', 'efgabchij', 'efghij'],
- # d = ['abc', 'efg', 'efg', 'abc', 'efg'], e
- # = ['abc', 'efg', 'efg', 'abc', 'hij', 'efg', 'hij']
复制代码 在分组外添加+后,量词会作用于整个分组,形成排列组合式的连续匹配。b中的+是贪婪匹配,匹配到abc后还会继续尝试efg,因此能得到abcefg、efgabc等结果;c允许abc、efg、hij三个备选连续出现。d和e在+后加?,变成非贪婪匹配,只匹配分组中的单个备选项即可,所以结果不再合并成连续组合。
如果要匹配固定次数组合,可以用{n}代替+。但固定次数仍可能匹配到abcabc、efgefg这类重复组合。此时可组合负向先行断言过滤重复:- import re
- a = "abcefg|abchij|abcabc|hijhij|efghij|efgefg"
- b = re.findall(r'''(?x)(?:abc(?!abc)|
- efg(?!efg)|hij(?!hij)){2}''', a)
- print("b = {}".
- format(b))
- # 输出结果:b = ['abcefg', 'abchij', 'efghij']
复制代码 这里的字符串a中,|只是普通字符分隔符。正则开头的(?x)是详细模式,会忽略正则中的换行、回车、tab和空格,因此模式可以分行书写。abc(?!abc)表示abc后面不能紧跟abc,efg(?!efg)和hij(?!hij)同理,用来过滤abcabc、efgefg、hijhij这类两个相同组合重复出现的情况。最终只保留abcefg、abchij、efghij。
四、字符类中的|是普通字符- import re
- a = "a1|2c2|3\\3"
- b = re.findall(r'[abc|]\d', a)
- c = re.findall(r'[abc\|]\d', a)
- print("b = {}, c = {}".format(b, c))
- # 输出结果:b = ['a1', '|2', 'c2', '|3'],
- # c = ['a1', '|2', 'c2', '|3']
复制代码 字符类中的每个字符本来就是“或”的关系,因此|在[abc|]中不再表示逻辑或,而是作为普通字符参与匹配。b和c的结果一致,说明在字符类中给|加反斜杠转义与不加转义效果相同,|都只代表它自身。
五、实践中的检查顺序
当re.findall结果与预期不一致时,可以先检查|是否被括号限制在局部,再检查量词作用于哪一个紧邻前项,以及是否使用了贪婪或非贪婪写法;如果|出现在字符类中,则要意识到它已失去逻辑或含义。这个排查顺序能覆盖本文示例中的主要差异:分支作用域、分组边界、量词组合和字符类语义。 |