一个python正则表达式字符串编码引发的血案

今天同事反馈,部分数据录入不了系统,我看了一下原始数据,经过 debug 初步断定是字符串输入半角全角问题,如 -(,导致正则匹配出了问题,大致一眼看去,还真看不出来呢,要不是用 python 内建函数 ord 查他的字符集编码,我还真不敢肯定,那么如何解决这个问题捏,先来说说我的思路,看我是如何一步步从入坑然后脱坑的

前脚入坑

python 提供了 re 模块,对正则表达式提供了非常好的支持,我们只需要定义好正则表达式的匹配规则,用起来非常轻松,并且 python 的 re 模块,对 r"(abc)" 这种规则的字符串还支持免特殊字符 \ 转义,用起来非常爽 (r表示原生字符串)

为了简化我解决问题的过程,我先提供一段简化后的代码,我们看看问题出在哪里

1
2
3
4
5
6
7
# coding=utf-8
import re
my_str = u"(123"
my_pattern_str = r"(()"
pattern = re.compile(my_pattern_str)
ret = pattern.match(my_str)
print ret.group(0)

输出结果是: None

纳尼?或许老鸟一眼就看出了玄机,请绕道,哈,新鸟可以和我一起一探究竟

r字符特性介绍

想完全了解问题我们先来了解一下 python 中正则表达式 r 特殊字符串的意义。当我们使用 r 字符串来处理正则匹配规则代码可以这样写

例子一

1
2
3
4
5
6
7
8
9
10
# coding=utf-8
import re

s = "test\-2汗001"
pattern = r"test\\-\d汗001"
match = re.compile(pattern)
ret = match.match(s)
print ret.group(0)

# 输出: test\-2汗001

例子二

如果我们不用 r 呢?那会麻烦一点

1
2
3
4
5
6
7
8
9
10
# coding=utf-8
import re

s = "test\-2汗001"
pattern = "test\\\\-\\d汗001"
match = re.compile(pattern)
ret = match.match(s)
print ret.group(0)

# 输出: test\-2汗001

对于一些特殊字符串我们需要多转义一次

排查问题

细心的同学可能已经发现,罪魁祸首是正则表达式字符串和待匹配字符串编码不一致,原例子中 my_pattern_str 的数据类型实际上是 utf-8 编码,而正则匹配表达式是一个 unicode 编码,去匹配的时候就出问题了,哈哈!!!我第一眼也没看出来问题,以为 python 可以支持

解决问题

解决问题的方法其实也很简单,从正则匹配表达式入手和从原数据入手

方法一

如果想从正则表达式的方向去解决问题,需要把正则表达式写为 unicode 编码(和需要匹配的字符串保持一致)

1
2
3
4
5
6
7
# coding=utf-8
import re
my_str = u"(123"
my_pattern_str = u"(()"
pattern = re.compile(my_pattern_str)
ret = pattern.match(my_str)
print ret.group(0)

但是这个方法我不推荐,毕竟每次需要去加转移还是挺蛋疼的

方法二

如果想从原始数据入手,把原始数据编码为正则表达式的编码就可以,这个方法我更推荐,因为用起来更方便,r 字符串的特性不能少,这个方法也有一个出错的可能性就是编码的时候出错,最好带上 ignore 参数,忽略错误的编码(一般不出错)

1
2
3
4
5
6
7
# coding=utf-8
import re
my_str = u"(123"
my_pattern_str = r"(()"
pattern = re.compile(my_pattern_str.decode("utf-8", "ignore"))
ret = pattern.match(my_str)
print ret.group(0)

总结

这只是一个小问题,开始我还以为某些特殊字符串 python 支持不够好,然后用特殊字符的 unicode 编码得到字符串然后做拼接,很明显把自己带到了坑里(虽然解决了问题,但是太麻烦了),在 python 里面定义正则表达式建议使用 utf-8 编码来定义,尤其是有特殊中文符号的情况,需要匹配的字符串通常都是 unicode 编码,所以还需要编码一次,这样可以保持编码一致

这里巩固了一遍 python 编码问题,加深了印象,下次总该不会再错了吧,这里还有一个小 tips,如果你的字符串 ascii 编码默认就支持(如数字,英文字母等),它就不会被转换为你制定的解码器编码,所以也就不需要再转换了(继续转换也不会出错)

其他

如果对 python 字符串编码还有疑问的可以复习下我博客文章 python2.x 编码,解码实战解说,里面对 python2.x 那让人抓狂的编码有更详细的说明

坚持原创技术分享,您的支持将鼓励我继续创作!