Python 2.7中无需修改默认编码处理Unicode对象UTF-8编码的方案
问题根源在于Python 2中,当对unicode字符串执行format操作且传入str类型(字节串)参数时,会自动使用默认ASCII编码将str解码为unicode,而你的name参数包含非ASCII字符,用ASCII解码必然失败。以下是几种无需修改系统默认编码的解决方法:
方案1:初始化时统一将输入转为unicode类型
在类的__init__方法中,把传入的name参数显式转换为unicode,指定正确的UTF-8编码,确保内部处理的都是unicode对象:
# coding: UTF-8 import logging root_logger= logging.getLogger() root_logger.setLevel(logging.DEBUG) handler = logging.FileHandler('example.log', 'w', 'utf-8') formatter = logging.Formatter('%(name)s %(message)s') handler.setFormatter(formatter) root_logger.addHandler(handler) class C(object): def __init__(self, name): # 显式将str转为unicode,指定UTF-8编码 if isinstance(name, str): self._name = name.decode('utf-8') else: self._name = name def __str__(self): print("__str__ start") return self.to_unicode().encode("utf-8") def __repr__(self): print("__repr__ start") return self.to_unicode().encode("utf-8") def to_unicode(self): print("to_unicode start") return u"name:{}".format(self._name) obj = C(name="vm_nearsync_한국") logging.debug(u"obj:{}".format(obj))
原理:后续to_unicode方法中格式化的是unicode类型的self._name,无需再自动解码,避免了ASCII编码的限制。
方案2:实现__unicode__方法替代直接在__str__中编码
Python 2中,当对unicode字符串执行format时,会优先调用对象的__unicode__方法(而非__str__)。我们可以实现__unicode__方法来处理unicode拼接,让__str__仅负责编码为字节串:
# coding: UTF-8 import logging root_logger= logging.getLogger() root_logger.setLevel(logging.DEBUG) handler = logging.FileHandler('example.log', 'w', 'utf-8') formatter = logging.Formatter('%(name)s %(message)s') handler.setFormatter(formatter) root_logger.addHandler(handler) class C(object): def __init__(self, name): self._name = name def __unicode__(self): print("__unicode__ start") # 在__unicode__中显式解码str类型的name if isinstance(self._name, str): return u"name:{}".format(self._name.decode('utf-8')) else: return u"name:{}".format(self._name) def __str__(self): print("__str__ start") return self.__unicode__().encode("utf-8") def __repr__(self): print("__repr__ start") return self.__unicode__().encode("utf-8") obj = C(name="vm_nearsync_한국") logging.debug(u"obj:{}".format(obj))
原理:logging.debug(u"obj:{}".format(obj))会直接调用__unicode__方法,在方法内我们指定用UTF-8解码str类型的name,绕过了默认的ASCII编码。
方案3:格式化时显式处理对象的unicode转换
如果无法修改类的实现,可以在日志格式化时显式将对象转为unicode,避免自动解码错误:
# 替代原日志行 logging.debug(u"obj:{}".format(unicode(obj)))
同时需要确保obj.__unicode__方法正确实现(参考方案2),或者手动处理转换:
def obj_to_unicode(obj): if isinstance(obj._name, str): return u"name:{}".format(obj._name.decode('utf-8')) else: return u"name:{}".format(obj._name) logging.debug(u"obj:{}".format(obj_to_unicode(obj)))
为什么不推荐修改系统默认编码?
修改sys.setdefaultencoding("utf-8")会全局改变Python的字符串自动转换规则,可能影响第三方库的行为(部分库依赖默认ASCII编码的行为),引发难以排查的隐性问题,因此不建议采用。
内容的提问来源于stack exchange,提问作者Sandeep Parmar

