python黑魔法之编码转换


Posted in Python onJanuary 25, 2016

我们在使用其他语言的库做编码转换时,对于无法理解的字符,通常的处理也只有两种(或三种):

  • 抛异常
  • 替换成替代字符
  • 跳过

但是在复杂的现实世界中,由于各种不靠谱,我们处理的文本总会出现那么些不和谐因素,比如混合编码。在这种情况下,又回到了上面的处理办法。

那么问题来了,python有没有更好地办法呢?

答案是,有!

python的编码转换流程实际上是两段式转换:

source -> unicode -> dest

首先将字符串从原始编码转换成unicode。再将unicode转换成目标编码。

第一步我们一般采用decode()或者 unicode() 这两个函数完成。
第二步我们使用encode()函数完成。

在这里我们说的黑魔法就是在第一步实现。

decode和unicode函数都有一个叫做errors的可选参数。看看官方的描述:

  • errors may be given to set a different error
  • handling scheme. Default is 'strict' meaning that encoding errors raise
  • a UnicodeDecodeError. Other possible values are 'ignore' and 'replace'
  • as well as any other name registered with codecs. register_error that is
  • able to handle UnicodeDecodeErrors.

这个参数通常有三种值:

  • strict 默认值。如果出现编码错误,则会抛出UnicodeDecodeError。
  • ignore 跳过。
  • replace 用?替换。

好了,看到最后一句话了吗?好戏上演了!

模块codec有一个函数叫做register_error。他的作用让用户可以注册自定义的errors处理方法。
用来处理UnicodeDecodeError。

我们看看函数原型:

codecs.register_error(name, error_handler)

name: 错误处理的名称。用以填写在decode函数的error参数中。
error_handler: 处理函数。该函数接受一个异常参数。
返回一个tuple,该tuple有2个元素,第一个是纠错后的字符串,第二个是继续decode的起始位置

有了上面的基本概念。我们看下具体实现:

def cjk_error(e):
  if not isinstance(e, UnicodeDecodeError):
    raise TypeError("don't know how to handle %r" % exc) 
  if exc.end + 1 > len(exc.object): 
    raise TypeError('unknown codec ,the object too short!') 
  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] 
  if 0x81<=ch1<=0xFE and (0x40<=ch2<=0x7E or 0x7E<=ch2<=0xFE): # GBK 
    return (unicode(sk,'cp936'), newpos) 
  if 0x81<=ch1<=0xFE and (0x40<=ch2<=0x7E or 0xA1<=ch2<=0xFE): # BIG5 
    return (unicode(sk,'big5'), newpos) 
  raise TypeError('unknown codec !') 
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 。错误发生在第三个字节

所以针对这种情况,做了下改进:

import codec

def cjk_replace(e):
  if not isinstance(e, UnicodeDecodeError):
    raise TypeError("invalid exception type %s" e)

  src = e.encoding
  if src in ('gbk','gb18030', 'big5'):
    beg = e.start - 2
    if beg >= 0:
      try:
        return unicode(e.object[beg:e.end], 'utf8'), e.end + 1
      except:
        pass

  if exc.end + 1 > len(exc.object):
    raise TypeError('unknown codec ,the object too short!')
  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]

  if src != 'gbk' and 0x81<=ch1<=0xFE and (0x40<=ch2<=0x7E or 0x7E<=ch2<=0xFE): # GBK
    return (unicode(sk,'cp936'), newpos)
  if src != 'big5' and 0x81<=ch1<=0xFE and (0x40<=ch2<=0x7E or 0xA1<=ch2<=0xFE): # BIG5
    return (unicode(sk,'big5'), newpos)
  raise TypeError('unknown codec !')

codecs.register_error("cjk_replace", cjk_replace)

当然,这个逻辑其实还是不够严谨的。虽然对于这种混合编码这种畸形活处理有点较真儿。
不过既然python提供这样的能力,大家可以一起来讨论下,我们怎么可以做的更好?

Python 相关文章推荐
Python实现TCP协议下的端口映射功能的脚本程序示例
Jun 14 Python
使用Python对MySQL数据操作
Apr 06 Python
Django与JS交互的示例代码
Aug 23 Python
Python 实现一行输入多个值的方法
Apr 21 Python
详解windows python3.7安装numpy问题的解决方法
Aug 13 Python
详解Python中is和==的区别
Mar 21 Python
Python: 传递列表副本方式
Dec 19 Python
Python turtle画图库&amp;&amp;画姓名实例
Jan 19 Python
pyqt5数据库使用详细教程(打包解决方案)
Mar 25 Python
Python发送邮件实现基础解析
Aug 14 Python
python模块内置属性概念及实例
Feb 18 Python
python中pandas.read_csv()函数的深入讲解
Mar 29 Python
Python编程中对文件和存储器的读写示例
Jan 25 #Python
Python开发如何在ubuntu 15.10 上配置vim
Jan 25 #Python
详解Python验证码识别
Jan 25 #Python
Python网站验证码识别
Jan 25 #Python
谈谈Python进行验证码识别的一些想法
Jan 25 #Python
基于Python Shell获取hostname和fqdn释疑
Jan 25 #Python
21行Python代码实现拼写检查器
Jan 25 #Python
You might like
地球防卫队:陪着奥特曼打小怪兽的人类力量 那些经典队服
2020/03/08 日漫
浅析PHP的静态成员函数效率更高的原因
2014/06/13 PHP
修改PHP脚本使WordPress拦截垃圾评论的方法示例
2015/12/10 PHP
java模拟PHP的pack和unpack类
2016/04/13 PHP
Thinkphp5框架实现图片、音频和视频文件的上传功能详解
2019/08/27 PHP
Prototype使用指南之ajax
2007/01/10 Javascript
JavaScript 轻松搞定快捷留言功能 只需一行代码
2010/04/01 Javascript
JS无限树状列表实现代码
2011/01/11 Javascript
ListBox实现上移,下移,左移,右移的简单实例
2014/02/13 Javascript
php析构函数的具体用法小结
2014/03/11 Javascript
在jquery中的ajax方法怎样通过JSONP进行远程调用
2014/04/04 Javascript
JavaScript实现获取dom中class的方法
2015/02/09 Javascript
jQuery实现自定义checkbox和radio样式
2015/07/13 Javascript
Vuejs第七篇之Vuejs过渡动画案例全面解析
2016/09/05 Javascript
JS实现倒计时(天数、时、分、秒)
2016/11/16 Javascript
JavaScript引用类型Object常见用法实例分析
2018/08/08 Javascript
微信小程序实现动态显示和隐藏某个控件功能示例
2018/12/14 Javascript
通过滑动翻页效果实现和移动端click事件问题
2021/01/26 Javascript
[52:57]2014 DOTA2国际邀请赛中国区预选赛 LGD-CDEC VS HGT
2014/05/21 DOTA
[52:00]2018DOTA2亚洲邀请赛 4.1 小组赛 A组加赛 LGD vs Optic
2018/04/02 DOTA
Python 网络编程起步(Socket发送消息)
2008/09/06 Python
pygame学习笔记(2):画点的三种方法和动画实例
2015/04/15 Python
django 多数据库配置教程
2018/05/30 Python
在python环境下运用kafka对数据进行实时传输的方法
2018/12/27 Python
python适合做数据挖掘吗
2020/06/16 Python
详解selenium + chromedriver 被反爬的解决方法
2020/10/28 Python
玩具反斗城西班牙网上商城:ToysRUs西班牙
2017/01/19 全球购物
FragranceNet中文网:北美健康美容线上零售商
2020/08/26 全球购物
银行实习自我鉴定
2013/10/12 职场文书
电子信息毕业生自荐信
2013/11/16 职场文书
大学生旅游业创业计划书
2014/01/29 职场文书
班主任班级寄语大全
2014/04/04 职场文书
服务承诺书怎么写
2014/05/24 职场文书
捐助感谢信
2015/01/22 职场文书
瘦西湖导游词
2015/02/03 职场文书
SQLServer RANK() 排名函数的使用
2022/03/23 SQL Server