[翻译]为什么sys.setdefaultencoding()会破坏代码

注:本文2016-12-28发布于个人搭建博客,现在将内容迁移过来,会有些许表述改动,未经同意,请勿转载。

原文Why sys.setdefaultencoding() will break code

我知道更聪明、更有经验的Python程序员之前已经向python-dev提了相关问题,但每次当我需要向别人引用其中一个时,我发现很难找得到。今天当我在Google上搜索这个问题时,发现最相关的条目是我自己在2011年发给yum-devel的一个帖子。我知道以后我肯定有必要向别人证明不应该使用setdefaultencoding()方法,所以为了避免下次再去网络上搜索,我决定在这里发表我的论证。

一些背景

15年以前:支持Unicode的Python问世

对Python2而言,一定程度下我们可以将字节串(str type)和字符串(unicode type)混为一谈,例如:

>>> u‘Toshio‘ == ‘Toshio‘
True
>>> print(u‘Toshio‘ + ‘ Kuratomi‘)
Toshio Kuratomi

当你执行这些操作时,Python发现一边是unicode类型,另一边是str类型,于是它取出str的值,将其解码成一个unicode类型,然后继续执行相应的操作。解析这些字节码的编码就是我们说的defaultencoding(根据sys.getdefaultencoding()命名,通过这个函数你可以查看当前的默认编码)。

当Python开发者第一次试验与str截然不同的unicode字符串时,他们不确定defaultencoding应该设置成什么。因此他们创建了sys.setdefaultencoding方法,这个方法在Python程序启动时被调用以试验不同的defaultencoding值带来的不同影响。Python的作者们通过改变自己的site.py文件,观察设置不同的默认编码对代码行为的影响,从而获取更多经验。

最终在2000年8月(写本文时已经过去了14年半),上述的Python试验版本正式成为Python-2.0,它的作者们决定将这个敏感的配置defaultencoding设置成ascii。

我知道今天再次去评价ascii的决定很容易,但是在14年以前字符编码风格比今天更混乱。新出现的编程语言和API已经针对unicode固定两个字节的编码规则进行了优化。但是针对特定自然语言的非unicode一字节编码在那时使用更加广泛。许多数据(甚至在今天)可以包含非ascii文本,而不去声明解码方式。在那个年代,任何游离ascii编码王国之外的人都需要被警醒:他们正进入一片编码恶魔肆意游荡的土地。ascii在许多跨越边界的情况下抛出错误,从而警告人们必须严加看管自己的代码。

然而,在Python-2.0带来unicode功能的同时,Python的作者们却渐渐发现有一个疏忽带来了很不好的影响。这个疏忽便是他们没有删除sys.setdefaultencoding()这个方法。为了弥补这个疏漏,他们在site.py中删除了sys的这一属性,从而避免人们在初始化以外的地方使用setdefaultencoding(),但是他们仍然可以在自己的site.py中改变defaultencoding。

sys.setdefaultencoding()的滥用

随着时间的推移,utf-8编码在Unix-like操作系统和网络传输中占据着统治地位。很多只需处理utf-8编码文本的人厌倦了字符串和字节串混在一起带来的错误。于是他们发现了setdefaultencoding()这根稻草,开始尝试用这种方式摆脱他们遇到的麻烦。

起初,有能力的程序员通过更新Python安装的全局文件site.py来使用setdefaultencoding (),这也是Python官方文档建议的用法,这只在用户自己的机器上有用。不幸的是,这些用户通常都是程序员,他们的程序需要在其他人的机器上运行,比如IT部门、客户以及遍布整个互联网的用户。这意味着更新site.py文件会使他们处于比以前更糟糕的境地:他们的代码在自己的机器上似乎工作良好,却在正真使用该软件的人那里运行奔溃。

由于程序员的关注点仅限于别人能否使用他们的软件,所以他们认为如果自己的软件可以将设置默认编码作为其初始化的一部分,那事情就好办多了。他们不必再强迫别人修改自己的Python安装,因为他们的软件会在运行时做出决定。于是乎他们重新审视了一下sys.setdefaultencoding()这个方法。虽然Python的作者们尽最大努力让这个方法在python启动后不可用,但程序员还是想到了获取这个功能的妙方:

import sys
reload(sys)
sys.setdefaultencoding(‘utf-8‘)

一旦这段代码运行,强制字节串转换成字符串的的默认编码将变为utf-8。这意味着当utf-8编码的字节串与unicode字符串混合时,Python将成功地将str类型数据转换为unicode类型,并将它们合并成一个unicode字符串。 这就是新一代的程序员对于他们大部分数据所期待的样子,所以用这几行(不可否认非常的hack)代码解决问题的想法对他们来说非常有吸引力。 不幸的是,这样做有很明显的缺点。

为什么sys.setdefaultencoding()会破坏你的代码

(1)编写一次,改变一切

sys.setdefaultencoding()带来的第一个问题乍一看不是很明显。当你使用这个方法时,即将运行的代码都将受到影响。你的代码,标准库的代码以及不受你管控的第三方代码都将在你设置的默认编码下运行。有些不是你负责的代码依赖的默认编码是ascii,此时它就不会抛出错误,很可能制造一些垃圾数据。比如,你依赖的第三方库有如下代码:

def welcome_message(byte_string):
    try:
        return u"%s runs your business" % byte_string
    except UnicodeError:
        return u"%s runs your business" % unicode(byte_string,
            encoding=detect_encoding(byte_string))

print(welcome_message(u"Angstrom (??)".encode("latin-1"))

如果没有改变默认编码,这段代码将无法通过ascii解码"?",随后进入异常处理,猜测编码并将其正确的转换成unicode字符串,程序会打印出 Angstrom (??) runs your business。一旦你将defaultencoding设置为utf-8,代码将使用utf-8解码数据,打印Angstrom (?) runs your business

当然,如果这段代码是在你自己的软件中,你完全有能力去处理这个编码问题。但是你并不能对第三方库做这些事情。

(2)我们正破坏字典

设置utf-8为默认编码带来的最严重的问题是破坏了字典的一些行为约定。我们来看下面这段代码:

def key_in_dict(key, dictionary):
    if key in dictionary:
        return True
    return False

def key_found_in_dict(key, dictionary):
    for dict_key in dictionary:
        if dict_key == key:
            return True
    return False

你认为输入参数相同两个函数的输出会一致吗?在Python中,如果你没有滥用sys.setdefaultencoding()这个方法,那问题答案是肯定的。

>>> # Note: the following is the same as d = {‘Café‘: ‘test‘} on
>>> # systems with a utf-8 locale
>>> d = { u‘Café‘.encode(‘utf-8‘): ‘test‘ }
>>> key_in_dict(‘Café‘, d)
True
>>> key_found_in_dict(‘Café‘, d)
True
>>> key_in_dict(u‘Café‘, d)
False
>>> key_found_in_dict(u‘Café‘, d)
__main__:1: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
False

但是如果我们使用sys.setdefaultencoding(‘utf-8‘) 又会发生什么呢?答案是上面的行为会遭到破坏:

>>> import sys
>>> reload(sys)
>>> sys.setdefaultencoding(‘utf-8‘)
>>> d = { u‘Café‘.encode(‘utf-8‘): ‘test‘ }
>>> key_in_dict(‘Café‘, d)
True
>>> key_found_in_dict(‘Café‘, d)
True
>>> key_in_dict(u‘Café‘, d)
False
>>> key_found_in_dict(u‘Café‘, d)
True

在使用in操作时,程序计算key的hash值然后对比hash值是否相等。在utf-8编码下,只有在ascii编码体系里的字符串的unicode和str的hash值是相等的,其他的字符集下字符串的unicode和str的hash值是不相等的。==则会将字节串解码成unicode然后再比较二者。当你调用sys.setdefaultencoding(‘utf-8‘)后,你便允许字节串以utf-8的方式转换成unicode,然后两个字符串对比后发现相等。这样做的后果是in==的测试产生了不同的结果,这与人们习惯的行为相差甚远,大多数人认为这打破了语言的基本约定。

所以Python 3是如何修复这个问题的呢?

你或许已经知道Python 3将默认编码从ascii转变成utf-8,那它如何避免==in带来的问题呢?答案是Python 3不再进行字节串(python3 bytes type)和字符串(python3 str type)之间的隐式转码了。由于这两种类型现在是完全分离的,所以上文进行的“包含测试”和“相等测试”都会返回False

$ python3
>>> a = {‘A‘: 1}
>>> b‘A‘ in a
False
>>> b‘A‘ == list(a.keys())[0]
False

起初在Python 2中,ascii编码体系下字节串和字符串是相等的,这看起来有些滑稽。但是请记住字节串只是一种数字类型,下面的代码并不能像你期望的那样工作:

>>> a = {‘1‘: ‘one‘}
>>> 1 in a
False
>>> 1 == list(a.keys())[0]
False

原文地址:https://www.cnblogs.com/mecforlove/p/10292094.html

时间: 2024-10-15 21:18:18

[翻译]为什么sys.setdefaultencoding()会破坏代码的相关文章

Python sys.setdefaultencoding('utf-8') 后就没输出

为了解决Python的 UnicodeDecodeError: 'ascii' codec can't decode byte ,我们可以加入以下代码. import sys reload(sys) sys.setdefaultencoding('utf-8') 但是在编辑的时候发现,普通的输出却不见了,如图 print 1都没反应. 查资料后解决,原来reload(sys)的时候,sys.stdout 这个参数被重置为了ipython 的对象,导致无法输出.因此可以用以下代码代替 import

python中sys.setdefaultencoding('utf-8')的作用

在python中,编码解码其实是不同编码系统间的转换,默认情况下,转换目标是Unicode,即编码unicode→str,解码str→unicode,其中str指的是字节流,而str.decode是将字节流str按给定的解码方式解码,并转换成utf-8形式,u.encode是将unicode类按给定的编码方式转换成字节流str.注意调用encode方法的是unicode对象,生成的是字节流:调用decode方法的是str对象(字节流),生成的是unicode对象.若str对象调用encode会默

Python中 sys.setdefaultencoding("utf8") 的作用详解

在处理中文数据,经常加入下面的代码: import sys reload(sys) sys.setdefaultencoding("utf8") 设置python默认字节流编/解码器按照utf8解码方式,把字节流编/解码为unicode: 具体来说,所起到的作用,可以用下面两个错误来解释: 在将字节流使用str()方法转换为str对象时,会调用默认的encode函数,如果没有上述系统的默认编码设置,则自动使用'ascii' codecs进行编码,对于非ascii编码的数据,比如utf8

# coding:utf-8与sys.setdefaultencoding()的区别

1,# coding:utf-8设置文件内容的编码2,sys.setdefaultencoding()设置python解析器默认的编码 首先python编解码的过程是: str.decode('str的编码') --> unicode unicode.encode('想要的编码') --> 想要的编码 换句话说就是:unicode就是中间态编码,只用先将str字符串decode成unicode之后,才能将其encode成其他编码格式 例子: # coding:utf-8 s = '中文字符'

python3 中的reload(sys)和sys.setdefaultencoding('utf-8')

通常我们为了防止出现乱码会进行一下操作 import sys reload(sys) sys.setdefaultencoding('utf-8') 但这是python2的写法,但是在python3中这个需要已经不存在了,这么做也不会什么实际意义. 如果你要这么做就会出现一下错误 sys.setdefaultencoding('utf-8') AttributeError: module 'sys' has no attribute 'setdefaultencoding' 在Python2.x

微信公众平台开发(35)(天气预报、股票查询、手机归属查询、在线听音乐、翻译、成绩查询功能)代码分享

微信公众平台开发应用(天气预报.股票查询.手机归属查询.在线听音乐.翻译.成绩查询功能) 原文: http://www.cnblogs.com/imaker/p/5491433.html 1.xml(信息返回用扩展语言XML来传递值) $postStr = $GLOBALS["HTTP_RAW_POST_DATA"]; //extract post data if (!empty($postStr)){ $postObj = simplexml_load_string($postStr

使用 Python 在 Caché 和 Sql Server 之间同步数据

任务目标:抽取 Caché 中的数据,导入 Sql Server 中. 遇到的问题: 1.UnicodeEncodeError: ‘ascii’ codec can’t encode characters in…… 当程序中出现非ascii编码时,python 的处理常常会报这样的错.补救代码如下: stdi,stdo,stde=sys.stdin,sys.stdout,sys.stderr reload(sys) sys.stdin,sys.stdout,sys.stderr=stdi,std

Python爬虫总结(二)常见数据类型及其解析方法

Python爬虫总结(二)常见数据类型 上一篇我们简单介绍了如何用Python发送 http/https 请求获取网上数据,从web上采集回来的数据的数据类型有很多种,主要有: 放在HTML里. 直接放在javascript里. 放在JSON里. 放在XML里. 注意:这里很多概念都是web前端开发里的,因为我们采集的大多数数据都来自web,因此了解一些前端知识还是挺有必要的. 下面我简单介绍下各种数据类型,并结合一些实例介绍它们的解析方法. 数据类型 放在HTML里 HTML即超文本标记语言,

python代码风格指南:pep8 中文翻译

摘要 本文给出主Python版本标准库的编码约定.CPython的C代码风格参见?PEP7.本文和?PEP 257 文档字符串标准改编自Guido最初的<Python Style Guide>, 并增加了Barry的?GNU Mailman Coding Style Guide的部分内容.本文会随着语言改变等而改变.许多项目都有自己的编码风格指南,冲突时自己的指南为准. 本文给出主Python版本标准库的编码约定.CPython的C代码风格参见PEP7. 本文和PEP 257 文档字符串标准改