0%

Python- 文本处理正则匹配

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]'
  • \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 个字符并检查是否包含匹配的样式。包含的匹配样式必须是定长的,意思就是 abca|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):如果给定的 idname 存在,将会尝试匹配 yes-pattern ,否则就尝试匹配 no-patternno-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
2
3
4
5
6
7
8
9
10
11
12
13
import re
pattern1 = re.compile(
pattern, # 模式字符串
flags=0 # 可选标志
)
pattern1 = re.compile(r"",re.X)

pattern.match(string) 等价于 re.match(pattern, string)
pattern = re.compile(r'^\d{3}-(\d{3-8})$') # Regular Expression 对象
pattern.match('010-123456').groups()
pattern.findall('020-123123')
#不区分大小写
name_re = re.compile(r"xiaoyi", re.I)

编译标志 -flags

标志:模式修正符,可以修改表达式的搜索,可任意组合。

re 的九个常量,'a', 'L' and 'u' 作为内联标记是相互排斥的

  • re.A:ASCII:只匹配 ASCII 字符
    • 二进制匹配,让 \w, \W, \b, \B, \d, \D, \s\S 只匹配 ASCII,而不是 Unicode。
    • 这只对字符串匹配模式有效,对字节匹配模式无效
  • re.IIGNORECASE :对大小写不敏感的匹配
  • re.LLOCALE:作本地化识别匹配。影响 \w, \W, \b, 和 \B
  • re.U:Unicode 匹配,冗余,现在 ALL is Unicode,OUT
  • re.MMULTILINE,多行模式, ^$ 不会被解释,锚点元字符 ^ $ 工作范围在每行的起始。
  • re.SDOTALL,S :使 . 匹配包括换行在内的所有字符
  • re.XVERBOSE:冗长模式,能够使用 REs 的 verbose 状态,使之被组织得更清晰易懂
  • re.T:TEMPLATE
  • re.DEBUG:显示编译时的 debug 信息,无内联标记

re.match()

re.match():从字符串的开始位置开始匹配,如果匹配成功,则返回一个 match 对象,否则返回 None。

  • 从 0 开始,只报告一次匹配。
1
2
3
re.match(pattern,  # 正则表达式
string, # 要匹配的字符串
flags=0) # 标志位,如是否区分大小写,多行匹配等

匹配对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
m = re.match(r"\d","adf")  # match 对象
m.string 待匹配的文本
m.re 匹配时使用的 pattern 对象(正则表达式)
m.pos 正则表达式搜索文本的开始位置
m.endpos 正则表达式搜索文本的结束位
m.lastindex
m.lastgroup

m.group() # 返回匹配的字符串
m.start() # 匹配的起始位置的索引
m.end() # 匹配的结束位置的索引
m.span() # 元组,匹配的起始和结束位置的索引

# 数字对于 search 的搜索到的序号
m.group(1,2)
m.groups() # 返回所有匹配结果字符串的元组
m.group(num) # 获得匹配后的字符串
m.groupdict()

m.start(2) # 匹配字符串在原始字符串的开始位置
m.end(1) # 匹配字符串在原始字符串的结束位置
m.span(2) #
1
2
3
4
5
6
p=re.compile(..)
m=p.match('ddd')
if m:
print(m.group())
else:
print("nothing matched")

re.search():扫描整个字符串,返回第一个成功的匹配 match 对象,没有匹配返回 None

  • re.match() 前者是只匹配字符串的开始,re.search() 后者是整个字符串搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
re.search(pattern,string,flags=0)
search(r'cat', 'dog cat dog')
match.group(0)
# 'cat'
其中,group(0)和group()效果相同,均为获取取得的字符串整体**
一般,m.group(N) 返回第N组括号匹配的字符。
而m.group() == m.group(0) == 所有匹配的字符,与括号无关,这个是API规定的。
**正则表达式中的三组括号把匹配结果分成三组**

-  group()  同 group(0) 就是匹配正则表达式整体结果
-  group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分。
-  group(num=0)    匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。
- groups()    返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。
- **没有匹配成功的,re.search()返回None**

re.findall()

re.findall():找到 re 的所匹配的所有子串,并将它们作为列表返回,如果没有匹配项,返回空列表。

  • match 和 search 是匹配一次,findall 匹配所有。
  • match 和 search 得到的是 match 对象,findall 得到的是列表
1
2
3
4
5
6
7
re.findall(pattern,
string,
flags=0)

re.findall(r'\bf[a-z]*', 'which foot or hand fell fastest')
# ['foot','fell','fastest']

re.fullmatch()

re.fullmatch():如果整个 string 匹配到 pattern,就返回一个相应的匹配对象,否则返回 None

1
2
3
4
5
re.fullmatch(
pattern,
string,
flags=0
)

re.finditer()

re.finditer():搜索字符串,找到正则表达式匹配的所有子串,以迭代器形式返回,每个迭代元素是 match 对象

1
2
3
re.finditer(pattern,
string,
flags=0)

re.split()

re.split():切片:用 pattern 对 string 进行分割,返回 列表。

  • 如果在 pattern 中捕获到括号,那么所有的组里的文字也会包含在列表里。
1
2
3
4
5
6
7
8
9
10
11
12
re.split(
pattern,
string,
maxsplit=0, # 分隔次数,默认为 0,不限制次数,有设置值时 maxsplit 次,剩下的字符全部作为列表的最后一个元素。
flags=0
)

>>> re.split(r'[,|;]','zhaang;cheng|li,li,|san;si')
['zhaang', 'cheng', 'li', 'li', '', 'san', 'si']

# flags
flags=re.I

re.sub()

re.sub():替换:返回通过使用 repl 替换 string 最左边非重叠出现的 pattern 而获得的字符串

  • 若未匹配到,则返回原 string
  • 在一个字符串中替换所有匹配正则表达式的字符,返回替换后的字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
re.sub(pattern, # 要匹配的模式
repl, # 要替换的模式,可以是字符串或函数,类似的“3”这样的反斜线加数字表示模式中捕获组的编号
string,
count=0, # 替换的最大次数,非负整数,默认 0 为所有匹配都会被替换
flags=0)



import re

text='今天是:11/28/2018'
print(re.sub(r'(\d+)/(\d+)/(\d+)',r'\3-\1-\2',text))
print(text)


import re

text='今天是:11/28/2018'
datepat=re.compile(r'(\d+)/(\d+)/(\d+)')
print(datepat.sub(r'\3-\1-\2',text))
print(text)

# 复杂情况可用回调函数
import re
from calendar import month_abbr
text='今天是:11/28/2018'
datepat=re.compile(r'(\d+)/(\d+)/(\d+)')

def change_date(m):
mon_name=month_abbr[int(m.group(1))]
return '{} {} {}'.format(m.group(3),mon_name,m.group(2))
print(datepat.sub(change_date,text))



# 替换回调函数的输入参数是一个匹配对象,由match()和find()返回。用.group()方法来提取匹配中特定的部分。该函数返回替换后的文本。

# 除了得到替换后的文本外,如果还想知道一共完成了多少次替换,可以使用re.subn()

import re
text='今天是:11/28/2018,昨天是11/27/2018'
datepat=re.compile(r'(\d+)/(\d+)/(\d+)')
new_text,n=datepat.subn(r'\3-\1-\2',text)
print(text)
print(new_text)
print(n)

import re
a='98 分'
res=re.sub(r"\d+","100",a,替换次数)
print(res)

re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):', r'static PyObject*\npy_\1(void)\n{', 'def myfunc():')
# 'static PyObject*\npy_myfunc(void)\n{'


>>> def dashrepl(matchobj):
... if matchobj.group(0) == '-': return ' '
... else: return '-'
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)
'Baked Beans & Spam'

re.subn()

re.subn():替换:与 sub 相同,但返回元组(新的字符串,替换次数)

1
2
3
4
5
6
re.subn(pattern,
repl,
string,
count=0,
flags=0
)

re.escape()

re.escape():转义 pattern 中的特殊字符。如果你想对任意可能包含正则表达式元字符的文本字符串进行匹配,它就是有用的。

  • 优先用原始字符串
1
2
print(re.escape('http://www.python.org'))
# http://www\.python\.org

re.purge()

re.purge():清除正则表达式缓存

常用正则

  • 具体应用时注意修正
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 中文字符
pattern = r'[\u4e00-\u9fa5]'
# 常见非英文字符Unicode编码范围:
# u4e00-u9fa5 (中文)
# u0800-u4e00 (日文)
# uac00-ud7ff(韩文)
[^\x00-\xff] # 双字节字符(包括汉字),用来计算字符串的字节长度
\n\s*\r # 空白行
<(\S*?)[^>]*>.*?</\1>|<.*? /> # HTML 标记
^\s*|\s*$ # 首尾空白字符

# 用户名
pattern=r'^[A-Za-z0-9_\-\u4e00-\u9fa5]+$'
# 密码,必须包含大小写字母和数字的组合,不能使用特殊字符,长度在 8~10 之间
pattern=r'^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$'

# 邮箱 email
pattern=r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$'

# 域名
pattern=r'[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?'
# 十六进制
pattern=r'^#?([a-f0-9]|[a-f0-9]{3})'


^[1-9]\d*$    # 匹配正整数
^-[1-9]\d*$   # 匹配负整数
^-?[1-9]\d*$   # 匹配整数
^[1-9]\d*|0$ # 匹配非负整数(正整数 + 0)
^-[1-9]\d*|0$   # 匹配非正整数(负整数 + 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$   # 匹配正浮点数
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$  # 匹配负浮点数
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$  # 匹配浮点数
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$   # 匹配非负浮点数(正浮点数 + 0)
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$  # 匹配非正浮点数(负浮点

pampy

pampy:优雅的模式匹配库

  • re 牛批

difflib

difflib:【标准库】,文件内容对比

1
2
3
4
5
6
7
8
with open("aa.txt","r") as f:
file1=f.read
with open("bb.txt","r") as f:
file2=f.read
d=difflib.Differ()
diff=d.compare(file1,file2)
diff2 = d.make_file(file1,file2)
print("\n".join(list(diff)))
  • 各个差异符号表示含义
    • ‘-‘: 包含在第一个序列行中,不包含在第二个序列行中
    • ‘+’: 包含在第二个序列行中,不包含在第一个序列行中
    • ‘’: 两个序列行一致
    • ‘?’: 标志两个序列行存在增量差异
    • ‘^’: 标志出两个序列存在的差异字符

filecmp

filecmp:目录对比

meld

meld: 有图形界面的一个不错的文件比较工具

fuzzywuzzy

fuzzywuzzy:模糊字符串匹配。

Levenshtein:快速计算编辑距离以及字符串的相似度。

1
2
from fuzzywuzzy import fuzz
print(fuzz.ratio("你好", "你好世界!"))
给博主来一杯卡布奇诺