python字符串与文本处理技巧(2):大小写敏感搜索、最短匹配、多行匹配、Unicode标准化

1. 字符串忽略大小写的搜索替换

  • re.findall(patter, string, flags=re.IGNORECASE)

当我们需要忽略字符串中的字母大小写进行模式搜索时可以采用如下方案:

import re
text = 'UPPER PYTHON, lower python, Mix Python'
target1 = re.findall('python', text, flags=re.IGNORECASE)
print(target1)
# >>> ['PYTHON', 'python', 'Python']

target2 = re.sub('python', 'snake', text, flags=re.IGNORECASE)
print(target2)
# >>> UPPER snake, lower snake, Mix snake

2. 最短匹配模式

如果我们正在试着用正则表达式匹配某个文本模式但是它找到的是模式的最长可能匹配。 而我们想修改它变成查找最短的可能匹配。这个问题一般出现在需要匹配一对分隔符之间的文本的时候(比如引号包含的字符串)。考虑如下的例子:

from re import compile

str_pat = compile(r'"(.*)"')
text1 = 'computer says "no."'
target1 = str_pat.findall(text1)
print(target1)
# >>> ['no.']

text2 = 'computer says "no.", but phone says "yes."'
target2 = str_pat.findall(text2)
print(target2)
# >>> ['no.", but phone says "yes.']

在这个例子中,模式 r'"(.*)"' 的意图是匹配被双引号包含的文本。 但是在正则表达式中*操作符是贪婪的,因此匹配操作会查找最长的可能匹配。 于是在第二个例子中搜索 text2 的时候返回结果并不是我们想要的。

为了修正这个问题,可以在模式中的*操作符后面加上?修饰符,就像这样:

str_pat_new = compile(r'"(.*?)"')
target3 = str_pat_new.findall(text2)
print(target3)
# >>> ['no.', 'yes.']

这样就使得匹配变成非贪婪模式,从而得到最短的匹配,也就是我们想要的结果。

Comment :  在一个模式字符串中,点(.)匹配除了换行外的任何字符。 然而,如果你将点(.)号放在开始与结束符(比如引号)之间的时候,那么匹配操作会查找符合模式的最长可能匹配。 这样通常会导致很多中间的被开始与结束符包含的文本被忽略掉,并最终被包含在匹配结果字符串中返回。 通过在 * 或者 + 这样的操作符后面添加一个 ? 可以强制匹配算法改成寻找最短的可能匹配。

3. 多行匹配模式

当我们试着使用正则表达式去匹配一大块的文本,并需要跨越多行去匹配。这个问题很典型的出现在:当我们用点(.)去匹配任意字符的时候,忘记了点(.)不能匹配换行符的事实。如果我们希望(.)可以匹配包括换行符在内的所有字符,应该指明re.DOTALL参数。例如:

import re

comment = re.compile(r'/\*(.*?)\*/')
text1 = '/*this is a comment */ and /* this is another comment */'
text2 = """ /* this is
a multiline commnet */ and /* this
sentense is added to this comments*/
"""
print(comment.findall(text1))
print(comment.findall(text2))
# >>> ['this is a comment ', ' this is another comment ']
# >>> []

"""修正问题,修改模式字符串,增加对换行的支持"""
comment_new = re.compile(r'/\*((?:.|\n)*?)\*/')
print(comment_new.findall(text2))
# >>>[' this is\na multiline commnet ', 
#     ' this\nsentense is added to this comments']

"""re.DOTALL可以使得正则化表达式中的(.)匹配包括换行符中的任意字符""""
comment_New = re.compile(r'/\*(.*?)\*/', re.DOTALL)
print(comment_New.findall(text2))
# >>>  [' this is\na multiline commnet ', 
#       ' this\nsentense is added to this comments']

在这个模式中, (?:.|\n) 指定了一个非捕获组 (也就是它定义了一个仅仅用来做匹配,而不能通过单独捕获或者编号的组)。com

Comment:对于简单的情况使用 re.DOTALL 标记参数工作的很好, 但是如果模式非常复杂或者是为了构造字符串令牌而将多个模式合并起来, 这时候使用这个标记参数就可能出现一些问题。

4. Unicode标准化

在处理Unicode字符串时,我们需要确保所有字符串在底层有相同的表示。

在Unicode中,某些字符能够用多个合法的编码表示。例如下面的例子:

s1 = 'Spicy Jalape\u00f1o'
s2 = 'Spicy Jalapen\u0303o'
print(s1, len(s1), s2, len(s2), s1==s2)
# >>> Spicy Jalapeño 14 Spicy Jalapeño 15 False

文本”Spicy Jalapeño”使用两种形式来表示。 前者使用整体字符”ñ”(U+00F1),后者使用拉丁字母”n”后面跟一个”~”的组合字符(U+0303)。在需要比较字符串的程序中使用字符的多种表示会产生问题。 为了修正这个问题,我们可以使用unicodedata模块先将文本标准化:

import unicodedata

s1 = 'Spicy Jalape\u00f1o'
s2 = 'Spicy Jalapen\u0303o'
print(s1, len(s1), s2, len(s2), s1==s2)
# >>> Spicy Jalapeño 14 Spicy Jalapeño 15 False

t1 = unicodedata.normalize('NFC', s1)
t2 = unicodedata.normalize('NFC', s2)
print(t1, len(t1), t2, len(t2), t1==t2)
# >>> Spicy Jalapeño 14 Spicy Jalapeño 14 True

print(ascii(t1), ascii(t2))
# >>> 'Spicy Jalape\xf1o' 'Spicy Jalape\xf1o'

normalize()。第一个参数指定字符串标准化的方式。 NFC表示字符应该是整体组成,当然也可以用NFD,NFD表示字符应该分解为多个组合字符表示。

Comment:标准化对于任何需要以一致的方式处理Unicode文本的程序都是非常重要的。 当处理来自用户输入的字符串,而我们很难去控制编码的时候尤其如此。 在清理和过滤文本的时候字符的标准化也是很重要的。 比如,假设我们想清除掉一些文本上面的变音符的时候(可能是为了搜索和匹配)。

文章参考 python3-codebook

你可能感兴趣的:(Python通关之路)