python黑魔法之编码转换
我们在使用其他语言的库做编码转换时,对于无法理解的字符,通常的处理也只有两种(或三种):
- 抛异常
- 替换成替代字符
- 跳过
但是在复杂的现实世界中,由于各种不靠谱,我们处理的文本总会出现那么些不和谐因素,比如混合编码。在这种情况下,又回到了上面的处理办法。
那么问题来了,python有没有更好地办法呢?
答案是,有!
python的编码转换流程实际上是两段式转换:
source->unicode->dest
首先将字符串从原始编码转换成unicode。再将unicode转换成目标编码。
第一步我们一般采用decode()或者unicode()这两个函数完成。
第二步我们使用encode()函数完成。
在这里我们说的黑魔法就是在第一步实现。
decode和unicode函数都有一个叫做errors的可选参数。看看官方的描述:
- errorsmaybegiventosetadifferenterror
- handlingscheme.Defaultis'strict'meaningthatencodingerrorsraise
- aUnicodeDecodeError.Otherpossiblevaluesare'ignore'and'replace'
- aswellasanyothernameregisteredwithcodecs.register_errorthatis
- abletohandleUnicodeDecodeErrors.
这个参数通常有三种值:
- strict默认值。如果出现编码错误,则会抛出UnicodeDecodeError。
- ignore跳过。
- replace用?替换。
好了,看到最后一句话了吗?好戏上演了!
模块codec有一个函数叫做register_error。他的作用让用户可以注册自定义的errors处理方法。
用来处理UnicodeDecodeError。
我们看看函数原型:
codecs.register_error(name,error_handler)
name:错误处理的名称。用以填写在decode函数的error参数中。
error_handler:处理函数。该函数接受一个异常参数。
返回一个tuple,该tuple有2个元素,第一个是纠错后的字符串,第二个是继续decode的起始位置
有了上面的基本概念。我们看下具体实现:
defcjk_error(e): ifnotisinstance(e,UnicodeDecodeError): raiseTypeError("don'tknowhowtohandle%r"%exc) ifexc.end+1>len(exc.object): raiseTypeError('unknowncodec,theobjecttooshort!') ch1=ord(exc.object[exc.start:exc.end]) newpos=exc.end+1 ch2=ord(exc.object[exc.start+1:newpos]) sk=exc.object[exc.start:newpos] if0x81<=ch1<=0xFEand(0x40<=ch2<=0x7Eor0x7E<=ch2<=0xFE):#GBK return(unicode(sk,'cp936'),newpos) if0x81<=ch1<=0xFEand(0x40<=ch2<=0x7Eor0xA1<=ch2<=0xFE):#BIG5 return(unicode(sk,'big5'),newpos) raiseTypeError('unknowncodec!') codecs.register_error("cjk_replace",cjk_replace)
上面这个是我从网上copy的。开始我觉得很不错,但是后来发现是个很不经推敲的算法。
比如utf8和gbk在前两个字节就有交集的部分。当一个utf8的字符串以gbk编码decode的时候,出现错误是从第三个字节开始(前两个字节也能够在gbk编码范围中对应到一个汉字)。
如:
a="你"#utf8编码:'\xe4\xbd\xa0' c=unicode(a[:2],'gbk')#正常返回 c=unicode(a,'gbk')#UnicodeDecodeError。错误发生在第三个字节
所以针对这种情况,做了下改进:
importcodec defcjk_replace(e): ifnotisinstance(e,UnicodeDecodeError): raiseTypeError("invalidexceptiontype%s"e) src=e.encoding ifsrcin('gbk','gb18030','big5'): beg=e.start-2 ifbeg>=0: try: returnunicode(e.object[beg:e.end],'utf8'),e.end+1 except: pass ifexc.end+1>len(exc.object): raiseTypeError('unknowncodec,theobjecttooshort!') ch1=ord(exc.object[exc.start:exc.end]) newpos=exc.end+1 ch2=ord(exc.object[exc.start+1:newpos]) sk=exc.object[exc.start:newpos] ifsrc!='gbk'and0x81<=ch1<=0xFEand(0x40<=ch2<=0x7Eor0x7E<=ch2<=0xFE):#GBK return(unicode(sk,'cp936'),newpos) ifsrc!='big5'and0x81<=ch1<=0xFEand(0x40<=ch2<=0x7Eor0xA1<=ch2<=0xFE):#BIG5 return(unicode(sk,'big5'),newpos) raiseTypeError('unknowncodec!') codecs.register_error("cjk_replace",cjk_replace)
当然,这个逻辑其实还是不够严谨的。虽然对于这种混合编码这种畸形活处理有点较真儿。
不过既然python提供这样的能力,大家可以一起来讨论下,我们怎么可以做的更好?