sys.setdefaultencoding('utf-8')的危险

在Python 2中有一个令人沮丧的设置sys.setdefaultencoding('utf-8')趋势。任何人都可以列出问题的真实例子吗? 像it is harmful论点it is harmful或者it hides bugs听起来不是很有说服力。

更新 :请注意,这个问题只是关于utf-8 ,这不是关于改变默认编码“一般情况下”。

如果可以,请给出一些代码示例。

原始的海报要求提供代码,certificate交换机是有害的 – 除了它“隐藏”与交换机无关的错误。

结论总结

根据我收集的经验和证据,这里是我得出的结论。

  1. 现在将默认编码设置为UTF-8是安全的 ,除了专门的应用程序,处理来自非unicode就绪系统的文件。

  2. 交换机的“官方”拒绝是基于绝大多数最终用户(而不是图书馆提供商) 不再相关的原因 ,所以我们应该停止阻止用户设置它。

  3. 在默认情况下正确处理Unicode的模型中工作比手动使用unicode API更适合于系统间通信的应用程序。

有效地, 非常频繁地修改默认编码避免了绝大多数用例中的许多用户头痛 。 是的,在某些情况下,处理多重编码的程序会默默地行为不端,但是由于这个开关可以被零碎地使用,所以在最终用户代码中不是问题

更重要的是,启用这个标志是一个真正的好处是用户的代码,通过减less手动处理Unicode转换的开销,混乱的代码,使其不太可读,而且避免程序员不能做到这一点时的潜在错误在所有情况下适当。


由于这些声明与Python的官方沟通渠道几乎完全相反,我认为对这些结论的解释是有必要的。

在野外成功使用修改的默认编码的例子

  1. Fedora的Dave Malcom相信它永远是对的。 他提议,在调查风险之后,为所有 Fedora用户改变分配范围def.enc。= UTF-8。

    尽pipe为什么 Python会打破这个硬性的事实,只不过是我列出的散列行为,在核心社区中,任何其他对手从来没有拿过这种散列行为,因此在使用用户门票时,担心甚至是由同一个人担心。

    Fedora的简历 :无可否认,这个变化本身被描述为与核心开发者“非常不受欢迎”,并被指责为与以前的版本不一致。

  2. openhub只有3000个项目 。 他们有一个缓慢的search前端,但扫描它,我估计98%使用UTF-8。 没有发现任何令人讨厌的惊喜。

  3. 有18000(!)Github主分支与它的变化。

    虽然这个变化在核心社区“ 不受欢迎 ”,但在用户群中却非常stream行。 虽然这可以被忽略,因为用户被认为使用hacky的解决scheme,我不认为这是一个有关的论点,由于我的下一个点。

  4. GitHub上总共只有150个错误报告。 有效率达到100%,这个变化似乎是正面的,而不是负面的。

    为了总结人们遇到的现有问题,我已经浏览了上述所有的门票。

    • Chaing def.enc。 UTF-8通常在问题解决过程中引入,但并未被移除,通常作为解决scheme。 考虑到“糟糕的新闻”,一些更大的人将其视为暂时的解决办法 ,但是更多的错误记者对这个问题 感到高兴 。

    • 一些(1-5?)项目修改他们的代码来手动进行types转换,以便他们不需要改变默认值。

    • 在两个例子中,我看到有人用def.enc声称。 设置为UTF-8会导致完全缺乏输出 ,而不解释testing设置。 我无法validation这个说法,而我testing了一个,发现相反是真实的。

    • 一个人声称他的“制度”可能取决于不改变它,但我们不知道为什么。

    • 一个(也只有一个)有一个真正的理由来避免它: ipython要么使用第三方模块,要么testing运行者以一种不受控制的方式修改它们的进程(绝不会有争议,def.enc。更改是由其支持者提倡的在翻译安装时,即当“拥有”该过程时)。

  5. 我发现零指示表示“é”和“u'é”的不同哈希值在真实代码中引起问题。

  6. Python不“打破”

    将设置更改为UTF-8之后,unit testing覆盖的Pythonfunction与​​不使用开关不同。 但是,交换机本身并没有经过testing。

  7. build议在bugs.python.org上让用户失望

    这里或这里的示例(通常与官方警告线相关)

    第一个例子说明这个转换在亚洲是如何build立起来的(与github的论点相比)。

  8. Ian Bicking 发表了他总是支持这种行为的支持。

    我可以使我的系统和通信一致UTF-8,事情会变得更好。 我真的没有看到一个缺点。 但是,为什么Python做到了这么难[…]我觉得有人认为他们比我聪明,但我不确定我是否相信他们。

  9. 马丁·法森在驳斥伊恩的同时承认 ,ASCII可能是错的。

    我相信,如果Python 2.5带有默认的UTF-8编码,那么它不会真的破坏任何东西。 但是如果我为我的Python做了这个工作,那么当我把代码给别人的时候,我就会遇到问题。

  10. 在Python3中,他们不会“练习他们所说的话”

    反对任何def.enc。 由于依赖于环境的代码或隐含性而变得如此苛刻, 这里的讨论围绕着Python3的“unicode sandwich”范式和相应的隐含假设问题展开讨论。

    此外,他们创造了编写有效的Python3代码的可能性,如:

     >>> from 褐褑褒褓褔褕褖褗褘 import * >>> def 空手(合氣道): あいき(ど(合氣道)) >>> 空手(う힑힜('👏 ') + 흾) 💔 
  11. DiveIntoPython 推荐它 。

  12. 在这个主题中 ,Guido自己build议 专业最终用户使用特定于stream程的环境,并将开关设置为“为每个项目创build自定义Python环境”。

    Python的2.x标准库的devise者不希望你能够在你的应用程序中设置默认编码的根本原因是标准库的编写是在默认的编码是固定的,并没有保证标准库的正确运行可以在您更改时进行。 这种情况没有testing。 没有人知道什么时候会失败。 如果标准库突然开始做你没想到的事情,那么你(或者更糟的是,你的用户)会回来抱怨我们。

  13. Jython提供即时更改,即使在模块中。

  14. PyPy 不支持重新加载(sys) – 但在一天之内将它带回 用户请求 ,而不会提出任何问题。 比较CPython的“ 你做错了 ”的态度,毫无例外地宣称它是“邪恶的根源”。


结束这个列表我确认可以构造一个模块, 因为改变了解释器的configuration而崩溃,像这样做:

 def is_clean_ascii(s): """ [Stupid] type agnostic checker if only ASCII chars are contained in s""" try: unicode(str(s)) # we end here also for NON ascii if the def.enc. was changed return True except Exception, ex: return False if is_clean_ascii(mystr): <code relying on mystr to be ASCII> 

我不认为这是一个有效的论据,因为编写这个双types接受模块的人明显知道ASCII与非ASCIIstring,并且知道编码和解码。

我认为这个证据足以说明,在绝大多数情况下 ,改变这个设置并不会导致真实代码库中的任何问题。

因为您并不总是希望将自己的string自动解码为Unicode,所以您的Unicode对象会自动编码为字节。 既然你要求一个具体的例子,这里是一个:

采取WSGInetworking应用程序; 您正在build立一个响应,通过将外部进程的产品添加到列表中,在一个循环中,并且该外部进程为您提供UTF-8编码的字节:

 results = [] content_length = 0 for somevar in some_iterable: output = some_process_that_produces_utf8(somevar) content_length += len(output) results.append(output) headers = { 'Content-Length': str(content_length), 'Content-Type': 'text/html; charset=utf8', } start_response(200, headers) return results 

这是伟大的,罚款和工作。 但是,你的同事来了,增加了一个新的function, 你现在也提供标签,而且这些标签是本地化的:

 results = [] content_length = 0 for somevar in some_iterable: label = translations.get_label(somevar) output = some_process_that_produces_utf8(somevar) content_length += len(label) + len(output) + 1 results.append(label + '\n') results.append(output) headers = { 'Content-Length': str(content_length), 'Content-Type': 'text/html; charset=utf8', } start_response(200, headers) return results 

你用英文testing了这个,一切都还可以,非常棒!

但是, translations.get_label()库实际上会返回Unicode值,而在切换区域设置时,标签将包含非ASCII字符。

由于您将setdefaultencoding()设置为UTF-8,所以WSGI库将这些结果写出到套接字中,并且所有的Unicode值都会自动编码,但计算的长度完全是错误的 。 如果UTF-8编码超过一个字节的ASCII范围以外的所有内容,它将会太短。

所有这些都忽略了实际上在不同编解码器中处理数据的可能性; 你可能会写出Latin-1 + Unicode,现在你有一个不正确的长度头混合的数据编码。

如果你没有使用sys.setdefaultencoding() ,会产生一个exception,你知道你有一个bug,但是现在你的客户抱怨不完整的回应; 在页面的末尾有字节丢失,你不知道如何发生。

请注意,这种场景甚至不涉及第三方库,这些库可能依赖于或不依赖于默认的ASCII码。 sys.setdefaultencoding()设置是全局的 ,适用于在解释器中运行的所有代码。 这些图书馆在隐式编码或解码方面有没有问题?

Python 2在strunicodetypes之间进行编码和解码,当您仅处理ASCII数据时,隐式地可以是有用和安全的。 但是你真的需要知道什么时候你偶然地混合了Unicode和字节串数据,而不是用全局的刷子来粘贴它,并希望最好。

首先:很多反对改变默认encryption的对手认为,它的愚蠢,因为它甚至改变ascii比较

我认为公平地表明,按照原来的问题,我认为除了偏离Ascii到UTF-8之外,没有任何人提倡任何其他的东西。

setdefaultencoding('utf-16')的例子似乎总是由反对改变它的人提出的;-)


用m = {'a':1,'é':2}和文件'out.py':

 # coding: utf-8 print u'é' 

然后:

 +---------------+-----------------------+-----------------+ | DEF.ENC | OPERATION | RESULT (printed)| +---------------+-----------------------+-----------------+ | ANY | u'abc' == 'abc' | True | | (ieAscii | str(u'abc') | 'abc' | | or UTF-8) | '%s %s' % ('a', u'a') | u'a a' | | | python out.py | é | | | u'a' in m | True | | | len(u'a'), len(a) | (1, 1) | | | len(u'é'), len('é') | (1, 2) [*] | | | u'é' in m | False (!) | +---------------+-----------------------+-----------------+ | UTF-8 | u'abé' == 'abé' | True [*] | | | str(u'é') | 'é' | | | '%s %s' % ('é', u'é') | u'é é' | | | python out.py | more | 'é' | +---------------+-----------------------+-----------------+ | Ascii | u'abé' == 'abé' | False, Warning | | | str(u'é') | Encoding Crash | | | '%s %s' % ('é', u'é') | Decoding Crash | | | python out.py | more | Encoding Crash | +---------------+-----------------------+-----------------+ 

[*]:结果假设是相同的 。 在下面看到。

在查看这些操作的同时,更改程序中的默认编码可能不会太糟糕,使您的结果更接近于仅使用Ascii数据。

关于散列(in)和len()行为,你可以在Ascii中得到相同的结果(更多关于下面的结果)。 这些操作还表明,unicode和字节string之间存在显着差异 – 如果您忽略,可能会导致逻辑错误。

正如已经指出的那样:这是一个全过程的select,所以你只需要一次select它 – 这就是为什么图书馆开发人员应该永远不要这样做,而是为了让他们的内部,以便他们不需要依靠python的隐式转换。 他们还需要清楚地logging他们期望和返回的内容,并且拒绝他们没有写入库的input(比如normalize函数,见下文)。

=>使用该设置编写程序会使其他人在其代码中使用程序的模块,至less不需要过滤input。

注意:有些对手声称def.enc。 甚至是一个系统范围的选项(通过sitecustomize.py),但最新的软件集装箱(docker)时代的每一个进程都可以在没有开销的完美环境中启动。


关于哈希和len()的行为:

它告诉你,即使修改def.enc。 你仍然不能不知道你在程序中处理的stringtypes。 u“和”'是内存中不同的字节序列 – 并不总是,但总的来说。

因此,在testing时,确保您的程序在非Ascii数据下也能正常运行。

有人说,当数据值发生变化时,哈希值可能变得不相等 – 尽pipe由于隐式转换,“==”操作保持不变 – 这是反对更改def.enc的一个论据。

我个人不认为这是因为散列行为与不改变散列行为一样。 还没有看到一个令人信服的例子,因为在我自己的一个过程中,这种设置是不符合要求的。

总而言之,关于setdefaultencoding(“utf-8”):关于它是否愚蠢的答案应该更加平衡。

这取决于。 虽然它避免了例如在日志语句中的str()操作中的崩溃,但是价格对于以后的意外结果来说是一个更高的机会,因为错误的types使得它更长的时间成为正确运行取决于某种types的代码。

在任何情况下,它都不应该是学习字节string和unicodestring之间区别的替代方法。


最后,将默认编码设置为远离Ascii不会让您的生活像len(),切片和比较这样的常见文本操作变得更加简单 – 您应该假设(byte)使用UTF-8对所有内容进行stringyfying解决问题。

不幸的是,它并没有 – 一般来说。

'=='和len()的结果比人们想象的要复杂得多,但是即使是双方都是相同的types。

没有def.enc。 更改,“==”失败总是非Ascii,如表中所示。 有了它,它有效 – 有时:

Unicode确实标准化了大约一百万个世界的符号,并给了它们一个数字 – 但遗憾的是在输出设备中显示给用户的字形与它们所产生的符号之间不存在1:1的双射。

为了激励你研究这个问题 :有两个文件,j1,j2使用相同的编码写入相同的程序,包含用户input:

 >>> u1, u2 = open('j1').read(), open('j2').read() >>> print sys.version.split()[0], u1, u2, u1 == u2 

结果:2.7.9JoséJoséFalse(!)

在Py2中使用print作为函数,你会看到原因:不幸的是有两种方法来编码相同的字符,重音符号“e”:

 >>> print (sys.version.split()[0], u1, u2, u1 == u2) ('2.7.9', 'Jos\xc3\xa9', 'Jose\xcc\x81', False) 

你可能会说什么愚蠢的编解码器,但不是编解码器的错误。 它在unicode中是一个问题。

所以即使在Py3中:

 >>> u1, u2 = open('j1').read(), open('j2').read() >>> print sys.version.split()[0], u1, u2, u1 == u2 

结果:3.4.2JoséJoséFalse(!)

=>独立于Py2和Py3,实际上独立于您使用的任何计算语言:要编写高质量的软件,您可能必须“正常化”所有用户input。 unicode标准确实规范了标准化。 在Python 2和3中,unicodedata.normalize函数是你的朋友。

真实的例子#1

它在unit testing中不起作用。

testing运行器( nosepy.test ,…)首先初始化sys ,然后才发现并导入你的模块。 那时改变默认编码已经太迟了。

同样的优点,如果有人把你的代码作为一个模块来运行,那么它就不起作用,因为它们的初始化是第一位的。

是的,混合和unicode并依靠隐式转换只会进一步下行的问题。

有一点我们应该知道的是

Python 2使用sys.getdefaultencoding()strunicode之间进行解码/编码

str和unicode之间的转换

所以如果我们改变默认的编码,会有各种不兼容的问题。 例如:

 # coding: utf-8 import sys print "你好" == u"你好" # False reload(sys) sys.setdefaultencoding("utf-8") print "你好" == u"你好" # True