Python 在正则表达式、匹配上的最佳实践。如:
re:【标准库】,正则表达式pampy:优雅的模式匹配库difflib:【标准库】,文件内容对比filecmp:目录对比meld: 有图形界面的一个不错的文件比较工具fuzzywuzzy:模糊字符串匹配。Levenshtein:快速计算编辑距离以及字符串的相似度。
概述
re
re:【标准库】,正则表达式
- 正则表达式可以拼接
regular expression:正则表达式:或简称 regex 或 regexp:是在一个主体字符串中从左到右匹配字符串时的一种样式
- 特点:
- 一种文本模式,包括普通字符和特殊字符(元字符)
- 应用:字符串匹配
- 表达文本的特征
- 查找或替换一组字符串
- 匹配字符串的全部或部分
语法
字符
- 一般字符,匹配自身
.:匹配除换行符外的任意单个字符re.DOTALL:将匹配包括换行符的任意字符^.$匹配一个非空行,在该行中可以包含除了换行符以外的任意字符。
^:行首,字符串开始位置,在 MULTILINE 模式匹配换行后的首个字符- 在方括号中为反义符,表示不接受该字符集合。
$:行尾,字符串结束位置,在 MULTILINE 模式匹配换行符的前一个字符\:转义字符:将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符
预定义字符
小写为是(bdsw),大写为非 (BDSW)
\A:字符串开始\b:单词边界,即字与空格间的位置,即 \w 与 \W 字符之间,或 \w 与字符串开始/结尾的边界r'\bfoo\b'匹配'foo','foo.','(foo)','bar foo baz'但不匹配'foobar'或者'foo3'。- 如果使用了 LOCALE 标志,则词的边界由当前语言区域设置。
\B:非单词边界r'py\B'匹配'python','py3','py2', 但不匹配'py','py.', 或者'py!'.\B是\b的取非
\d:匹配数字字符,即[0-9]和其他数字字符- 如果设置了 ASCII 标志,则只匹配
[0-9] r'[01]\d\d|2[0-4]\d|25[0-5]'
- 如果设置了 ASCII 标志,则只匹配
\D:匹配非数字字符,即[^0-9]\s:任意空白字符,类[\t\n\r\f\v]包括空格、制表符、换页符等等。\S:任意非空白字符,类[^\t\n\r\f\v]\w:任意字母数字下划线,即[a-zA-Z0-9_]- 匹配包括下划线的任何单词字符
- ^\w$ 匹配一个非空行,并且该行中只能包含字母、数字、下划线和汉字中的任意字符。
- Attention:对于 unicode 的字符,unicode 的汉字由字母与数字构成。
\w能匹配汉字,需加一个 re.A,只匹配[a-zA-Z0-9_]
\W: 任意非字母数字字符,类[^a-zA-Z0-9]\Z:只匹配字字符串尾
原义字符 \^
将元字符转为原义字符,匹配一些保留的字符: [ ] ( ) { } . * + ? ^ $ \ |
标准转义字符 \n
\n:换行符\r:回车符\t:制表符\v: 匹配一个垂直制表符\f:换页符\p:匹配 CR/LF(等同于\r\n),用来匹配 DOS 行终止符\cx匹配由 x 指明的控制字符。例如,\cM匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。
选择符: | ()
A|B可以匹配 A 或 B,A、B 是任意正则表达式,即(P|p)ython可以匹配'Python'或者'python'。- 一旦 A 匹配成功,B 就不再进行匹配,即使它能产生一个更好的匹配。
():(xyz)字符串,子表达式,组合。[]:[abc3-9]:字符集,匹配方括号内的任意字符- 元字符在字符集中不起作用,如
a[.]匹配a.- 但
]要用[\]],加个反斜杠
- 但
- -:<H[1-6]> HTML 标签的 H1-H6
- 取反:
[^5]:否定字符,匹配除了方括号内的任意字符 - 常用:
[0-9a-zA-Z\_\-]可以匹配一个数字、字母、下划线、连字符;[abc][^cfC],[\u4e00-\u9fa5](汉字)
- 元字符在字符集中不起作用,如
-连字符,用于字符集中,[0-9]等价于[0123456789][a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是 Python 合法的变量;
限定符 *+?{}
限定符:限定上一个字符模式可以出现的次数。
贪婪匹配:Greedy matching:正则表达式默认对限定符采用贪婪模式,即会匹配尽可能长的子串
惰性匹配:lazy matching:非贪婪匹配?将贪婪模式转化为惰性匹配,尽可能少地匹配满足的字符。
- 默认贪婪匹配
*:上一个字符模式出现零次或多次,任意次。贪婪匹配,尽可能多的次数*?:非贪婪模式,尽可能少的使用重复的第一个匹配,:
+: 一次或多次 。至少一次,贪婪匹配+?:非贪婪模式,尽可能少的使用重复但至少使用一次
?:零次或一次,用于标识该字符是可选的。或指定一个非贪婪模式(最小匹配)。??:非贪婪模式,如果有可能使用零次重复或一次重复。
{m}:恰好 m 次{m,}:至少 m 次{m, n}:至少 m 次,至多 n 次,{m}?: 等同于{m} {m,}? 尽可能少的使用重复但至少重复 m 次{m, n}?: 介于 n 次和 m 次之间,尽可能少的使用重复。
扩展 (?...)
Python re 还具有一些扩展,如 扩展也牛批(?aiLmsux),(?:…), (?P=name) 等,这里不加赘述,用好上面的能实现 99% 的功能了。
(?aiLmsux):a,i,L,m,s,u,x中的一个或多个,该组合匹配一个空字符串,- 这些字符对正则表达式设置以下标记 re.A (只匹配 ASCII 字符), re.I (忽略大小写), re.L (语言依赖), re.M (多行模式), re.S (点 dot 匹配全部字符), re.U (Unicode 匹配), and re.X (冗长模式)。
- 如果你想将这些标记包含在正则表达式中,这个方法就很有用,免去了在 re.compile() 中传递 flag 参数。标记应该在表达式字符串首位表示。
(?:...):正则括号的非捕获版本,匹配在括号内的任何正则表达式,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用。(?aiLmsux-imsx:…):('a','i','L','m','s','u','x'中的 0 或者多个, 之后可选跟随'-'在后面跟随'i','m','s','x'中的一到多个 .) 这些字符为表达式的其中一部分 设置 或者 去除 相应标记 re.A (只匹配 ASCII 字符), re.I (忽略大小写), re.L (语言依赖), re.M (多行模式), re.S (点 dot 匹配全部字符), re.U (Unicode 匹配), and re.X (冗长模式)(?P<name>…):(命名组合)类似正则组合,但是匹配到的子串组在外部是通过定义的 name 来获取的。组合名必须是有效的 Python 标识符,并且每个组合名只能用一个正则表达式定义,只能定义一次。一个符号组合同样是一个数字组合,就像这个组合没有被命名一样。命名组合可以在三种上下文中引用。如果样式是(?P<quote>['"]).*?(?P=quote)(也就是说,匹配单引号或者双引号括起来的字符串):引用组合 “quote” 的上下文引用方法在正则式自身内(?P=quote)(如示)\1处理匹配对象 mm.group('quote')``m.end('quote')(等) 传递到re.sub()里的 repl 参数中\g<quote>``\g<1>``\1(?P=name):反向引用一个命名组合;它匹配前面那个叫 name 的命名组中匹配到的串同样的字串。(?#…):注释;里面的内容会被忽略。(?=…):匹配…的内容,但是并不消费样式的内容。这个叫做 lookahead assertion。比如,Isaac (?=Asimov)匹配'Isaac '只有在后面是'Asimov'的时候。(?!…):匹配…不符合的情况。这个叫 negative lookahead assertion (前视取反)。比如说,Isaac (?!Asimov)只有后面 不 是'Asimov'的时候才匹配'Isaac '。(?<=…):匹配字符串的当前位置,它的前面匹配…的内容到当前位置。这叫:dfn:positive lookbehind assertion (正向后视断定)。(?<=abc)def会在'abcdef'中找到一个匹配,因为后视会往后看 3 个字符并检查是否包含匹配的样式。包含的匹配样式必须是定长的,意思就是abc或a|b是允许的,但是a*和a{3,4}不可以。注意以 positive lookbehind assertions 开始的样式,如(?<=abc)def,并不是从 a 开始搜索,而是从 d 往回看的。(?<!…):匹配当前位置之前不是…的样式。这个叫:dfn:negative lookbehind assertion (后视断定取非)。类似正向后视断定,包含的样式匹配必须是定长的。由 negative lookbehind assertion 开始的样式可以从字符串搜索开始的位置进行匹配。(?(id/name)yes-pattern|no-pattern):如果给定的 id 或 name 存在,将会尝试匹配yes-pattern,否则就尝试匹配no-pattern,no-pattern可选,也可以被忽略。比如,(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$)是一个 email 样式匹配,将匹配'<user@host.com>'或'user@host.com',但不会匹配'<user@host.com',也不会匹配'user@host.com>'。- 由
'\'和一个字符组成的特殊序列在以下列出。 如果普通字符不是 ASCII 数位或者 ASCII 字母,那么正则样式将匹配第二个字符。比如,\$匹配字符'$'. \number
匹配数字代表的组合。每个括号是一个组合,组合从 1 开始编号。比如(.+) \1匹配'the the'或者'55 55', 但不会匹配'thethe'(注意组合后面的空格)。这个特殊序列只能用于匹配前面 99 个组合。如果 number 的第一个数位是 0, 或者 number 是三个八进制数,它将不会被看作是一个组合,而是八进制的数字值。在'['和']'字符集合内,任何数字转义都被看作是字符。
优先级
| 运算符 | 说明 |
| ——————————————– | ———————————————————————————– | ——– |
| \ | 转义符 |
| (), (?:), (?=), [] | 括号和中括号 |
| *, +, ?, {n}, {n,}, {n,m} | 限定符 |
| ^, $, *anymetacharacter, anycharacter* | 定位点和序列 |
| | | 替换 |
| 字符的优先级比替换运算符高,替换运算符允许“m | food”与“m”或“food”匹配。若要匹配“mood”或“food”,请使用括号创建子表达式,从而产生“(m | f)ood”。 |
方法
re.compile
re.compile():将正则表达式编译成正则表达式(Pattern)对象,供re.match()与re.search()等使用r'\n':原始字符串:以 r 修饰的普通字符串为原始类型字符串,再简单的表达式,也要使用原始字符串。
- 建议先编译再匹配,重复使用,加快速度。且更直观
1 | import re |
编译标志 -flags
标志:模式修正符,可以修改表达式的搜索,可任意组合。
re 的九个常量,'a', 'L' and 'u' 作为内联标记是相互排斥的
re.A:ASCII:只匹配 ASCII 字符- 二进制匹配,让
\w,\W,\b,\B,\d,\D,\s和\S只匹配 ASCII,而不是 Unicode。 - 这只对字符串匹配模式有效,对字节匹配模式无效
- 二进制匹配,让
re.I:IGNORECASE:对大小写不敏感的匹配re.L:LOCALE:作本地化识别匹配。影响 \w, \W, \b, 和 \Bre.U:Unicode 匹配,冗余,现在 ALL is Unicode,OUTre.M:MULTILINE,多行模式,^与$不会被解释,锚点元字符^$工作范围在每行的起始。re.S:DOTALL,S:使.匹配包括换行在内的所有字符re.X:VERBOSE:冗长模式,能够使用 REs 的 verbose 状态,使之被组织得更清晰易懂re.T:TEMPLATEre.DEBUG:显示编译时的 debug 信息,无内联标记
re.match()
re.match():从字符串的开始位置开始匹配,如果匹配成功,则返回一个 match 对象,否则返回 None。
- 从 0 开始,只报告一次匹配。
1 | re.match(pattern, # 正则表达式 |
匹配对象
1 | m = re.match(r"\d","adf") # match 对象 |
1 | p=re.compile(..) |
re.search()
re.search():扫描整个字符串,返回第一个成功的匹配 match 对象,没有匹配返回 None
- re.match() 前者是只匹配字符串的开始,re.search() 后者是整个字符串搜索
1 | re.search(pattern,string,flags=0) |
re.findall()
re.findall():找到 re 的所匹配的所有子串,并将它们作为列表返回,如果没有匹配项,返回空列表。
- match 和 search 是匹配一次,findall 匹配所有。
- match 和 search 得到的是 match 对象,findall 得到的是列表
1 | re.findall(pattern, |
re.fullmatch()
re.fullmatch():如果整个 string 匹配到 pattern,就返回一个相应的匹配对象,否则返回 None
1 | re.fullmatch( |
re.finditer()
re.finditer():搜索字符串,找到正则表达式匹配的所有子串,以迭代器形式返回,每个迭代元素是 match 对象
1 | re.finditer(pattern, |
re.split()
re.split():切片:用 pattern 对 string 进行分割,返回 列表。
- 如果在 pattern 中捕获到括号,那么所有的组里的文字也会包含在列表里。
1 | re.split( |
re.sub()
re.sub():替换:返回通过使用 repl 替换 string 最左边非重叠出现的 pattern 而获得的字符串
- 若未匹配到,则返回原 string
- 在一个字符串中替换所有匹配正则表达式的字符,返回替换后的字符串。
1 | re.sub(pattern, # 要匹配的模式 |
re.subn()
re.subn():替换:与 sub 相同,但返回元组(新的字符串,替换次数)
1 | re.subn(pattern, |
re.escape()
re.escape():转义 pattern 中的特殊字符。如果你想对任意可能包含正则表达式元字符的文本字符串进行匹配,它就是有用的。
- 优先用原始字符串
1 | print(re.escape('http://www.python.org')) |
re.purge()
re.purge():清除正则表达式缓存
常用正则
- 具体应用时注意修正
1 | # 中文字符 |
pampy
pampy:优雅的模式匹配库
- re 牛批
difflib
difflib:【标准库】,文件内容对比
1 | with open("aa.txt","r") as f: |
- 各个差异符号表示含义
- ‘-‘: 包含在第一个序列行中,不包含在第二个序列行中
- ‘+’: 包含在第二个序列行中,不包含在第一个序列行中
- ‘’: 两个序列行一致
- ‘?’: 标志两个序列行存在增量差异
- ‘^’: 标志出两个序列存在的差异字符
filecmp
filecmp:目录对比
meld
meld: 有图形界面的一个不错的文件比较工具
fuzzywuzzy
fuzzywuzzy:模糊字符串匹配。
Levenshtein:快速计算编辑距离以及字符串的相似度。
1 | from fuzzywuzzy import fuzz |
