关于python:sys.setdefaultencoding(‘utf-8’)的危险

Dangers of sys.setdefaultencoding('utf-8')

在python 2中设置sys.setdefaultencoding('utf-8')有一种令人沮丧的趋势。有人能列举出问题的真实例子吗?像it is harmfulit hides bugs这样的论点听起来不太有说服力。

更新:请注意,这个问题只与utf-8有关,而不是"在一般情况下"更改默认编码。

如果可以的话,请用代码给出一些例子。


最初的海报要求提供代码,证明交换机是有害的,只是它"隐藏"了与交换机无关的错误。好的。结论概要

根据我收集到的经验和证据,以下是我得出的结论。好的。

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

  • "官方"拒绝转机是基于不再与绝大多数最终用户(而不是图书馆供应商)相关的原因,因此我们应该停止劝阻用户设置转机。好的。

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

  • 实际上,在绝大多数用例中,经常修改默认编码可以避免许多用户头痛。是的,在某些情况下,处理多个编码的程序会无声地出错,但是由于可以逐段启用此开关,所以这在最终用户代码中不是问题。好的。

    更重要的是,启用这个标志是用户代码的一个真正优势,既可以减少手工处理Unicode转换的开销,使代码混乱,降低可读性,也可以避免程序员在任何情况下都不能正确执行这一操作时可能出现的错误。好的。

    由于这些声明与Python的官方通信线路几乎完全相反,我认为对这些结论的解释是有必要的。好的。在野外成功使用修改过的默认编码的示例

  • 费多拉的戴夫·马尔科姆认为这永远是对的。在调查了风险之后,他建议为所有Fedora用户更改分布范围的def.enc.=utf-8。好的。

    尽管我列出的散列行为只是为什么python会崩溃,但在处理用户票据时,核心社区中的任何其他对手都不会因为担心或甚至担心同一个人而采取这种行为。好的。

    Fedora的简历:诚然,改变本身被核心开发人员描述为"非常不受欢迎",并且被指控与以前的版本不一致。好的。

  • 仅OpenHub就有3000个项目在做这件事。他们的前端搜索速度很慢,但我估计98%的人使用的是UTF-8。没有发现令人讨厌的惊喜。好的。

  • 有18000个!!)Github主分支已更改。好的。

    虽然这一变化在核心社区"不受欢迎",但在用户群中却相当流行。尽管可以忽略这一点,但由于用户都知道使用黑客解决方案,我认为这不是一个相关的论点,因为我的下一点。好的。

  • 因此,GitHub上总共只有150个错误报告。以100%的有效率,变化似乎是积极的,而不是消极的。好的。

    为了总结人们遇到的现有问题,我已经浏览了前面提到的所有门票。好的。

    • 转换def.enc.to utf-8通常是在问题关闭过程中引入的,但不是删除的,通常作为解决方案。一些大公司把它当作临时修复,考虑到它的"坏媒体",但更多的bug记者对修复感到高兴。好的。

    • 几个(1-5?)项目修改了他们的代码,手工进行类型转换,这样他们就不再需要更改默认值了。好的。

    • 在两个例子中,我看到有人声称,将def.enc.set设置为utf-8会导致完全没有输出,而没有解释测试设置。我无法证实这一说法,我测试了一个,发现相反的说法是正确的。好的。

    • 有人说他的"系统"可能依赖于不改变它,但我们不知道为什么。好的。

    • 有一个(而且只有一个)有真正的理由避免它:ipython或者使用第三方模块,或者测试运行程序以不受控制的方式修改了它们的过程(没有人怀疑def.enc.change是由它的支持者在解释器设置时倡导的,即当"拥有"这个过程时)。好的。

  • 我发现零迹象表明"_"和"U"的不同散列在现实代码中会导致问题。好的。

  • python不会"中断"好的。

    在将设置更改为utf-8之后,单元测试所涉及的Python的任何特性都不会与不使用开关的情况有任何不同。不过,开关本身根本没有经过测试。好的。

  • 它在bugs.python.org上被建议给受挫的用户。好的。

    这里,这里或这里的例子(通常与官方警告线相关)好的。

    第一个例子展示了如何在亚洲建立交换机(也与Github的论点进行比较)。好的。

  • 伊恩·比金发表了他的支持,支持人们总是支持这种行为。好的。

    I can make my systems and communications consistently UTF-8, things will just get better. I really don't see a downside. But why does Python make it SO DAMN HARD [...] I feel like someone decided they were smarter than me, but I'm not sure I believe them.

    Ok.

  • 马蒂金·法森在驳斥伊恩时承认,ASCII一开始可能是错误的。好的。

    I believe if, say, Python 2.5, shipped with a default encoding of UTF-8, it wouldn't actually break anything. But if I did it for my Python, I'd have problems soon as I gave my code to someone else.

    Ok.

  • 在python3,他们不"实践他们所宣扬的"好的。

    尽管反对任何定义enc.change由于环境相关的代码或隐含性而如此严厉,但这里的讨论围绕着python3的"unicode三明治"范式的问题以及相应的所需的隐含假设展开。好的。

    此外,他们还创造了编写有效python3代码的可能性,比如:好的。

    1
    2
    3
    4
    >>> from 褐褑褒褓褔褕褖褗褘 import *        
    >>> def 空手(合氣道): あいき((合氣道))
    >>> 空手(う??('?? ') + ?)
    ??
  • Diveintopython推荐。好的。

  • 在这个线程中,guido自己建议专业的最终用户使用特定于流程的环境,并将开关设置为"为每个项目创建自定义的Python环境"。好的。

    The fundamental reason the designers of Python's 2.x standard library don't want you to be able to set the default encoding in your app, is that the standard library is written with the assumption that the default encoding is fixed, and no guarantees about the correct workings of the standard library can be made when you change it. There are no tests for this situation. Nobody knows what will fail when. And you (or worse, your users) will come back to us with complaints if the standard library suddenly starts doing things you didn't expect.

    Ok.

  • Jython提供即时更改,甚至在模块中。好的。

  • Pypy不支持Reload(sys),但在一天内根据用户的请求恢复了它,并且没有提出任何问题。与塞顿"你做错了"的态度相比,毫无证据地宣称这是"邪恶的根源"。好的。

  • 结束此列表后,我确认可以构建一个模块,该模块由于解释程序配置更改而崩溃,执行如下操作:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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>

    我不认为这是一个有效的论点,因为编写这个双类型接受模块的人明显知道ASCII和非ASCII字符串,并且知道编码和解码。好的。

    我认为,这些证据足以表明,在绝大多数情况下,改变这个设置不会导致现实世界中的代码库出现任何问题。好的。好啊。


    因为您并不总是希望字符串自动解码为Unicode,或者为此,您的Unicode对象自动编码为字节。由于您需要一个具体的例子,下面是一个:

    以一个wsgi Web应用程序为例;您正在通过在循环中将外部进程的产品添加到列表中来构建响应,并且该外部进程为您提供了utf-8编码字节:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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

    很好,很好,很有效。但随后,你的同事来了,并添加了一个新功能;你现在也提供了标签,这些都是本地化的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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 + '
    '
    )
        results.append(output)

    headers = {
        'Content-Length': str(content_length),
        'Content-Type': 'text/html; charset=utf8',
    }
    start_response(200, headers)
    return results

    你用英语测试过这个,一切都正常,太好了!

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

    wsgi库将这些结果写入套接字,所有Unicode值都会自动编码,因为您将setdefaultencoding()设置为utf-8,但计算的长度完全错误。它太短了,因为UTF-8用多个字节对ASCII范围之外的所有内容进行编码。

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

    如果您没有使用sys.setdefaultencoding(),就会出现一个异常,您知道您有一个bug,但现在您的客户抱怨响应不完整;页面末尾缺少字节,您不太清楚是怎么回事。

    请注意,此方案甚至不涉及可能依赖或不依赖默认值仍为ASCII的第三方库。sys.setdefaultencoding()设置为全局设置,适用于解释器中运行的所有代码。您如何确定这些库中没有涉及隐式编码或解码的问题?

    当您只处理ASCII数据时,python 2隐式地在strunicode类型之间进行编码和解码是有益和安全的。但是,您真的需要知道什么时候意外地混合了Unicode和字节字符串数据,而不是用全局刷覆盖它,并希望得到最好的结果。


    首先:许多反对更改默认ENC的人认为它是愚蠢的,因为它甚至改变了ASCII比较好的。

    我认为,公平地说,按照最初的问题,我认为除了从ASCII到UTF-8之外,没有人主张其他任何东西。好的。

    setDefaultEncoding("utf-16")示例似乎总是由那些反对更改它的人提出;-)好的。

    使用m='a':1,:2和文件'out.py':好的。

    1
    2
    # coding: utf-8
    print u'é'

    然后:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    +---------------+-----------------------+-----------------+
    | DEF.ENC       | OPERATION             | RESULT (printed)|            
    +---------------+-----------------------+-----------------+
    | ANY           | u'abc' == 'abc'       | True            |    
    | (i.e.Ascii    | 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数据。好的。

    关于hashing(in)和len()行为,您将得到相同的结果,然后是ASCII(下面的更多结果)。这些操作还表明Unicode字符串和字节字符串之间存在显著的差异,如果被忽略,这可能会导致逻辑错误。好的。

    如前所述:这是一个全过程的选项,所以您只需一次选择它——这就是为什么库开发人员永远不应该这样做,而是要使其内部结构井然有序,这样他们就不需要依赖于Python的隐式转换。他们还需要清楚地记录他们所期望的,并返回和拒绝他们没有为之编写lib的输入(如normalize函数,见下文)。好的。

    =>在设置为"开"的情况下编写程序会使其他人在代码中使用程序的模块(至少不过滤输入)带来风险。好的。

    注意:一些反对者声称def.enc.甚至是一个系统范围的选项(通过sitecustomize.py),但软件容器化(docker)的最新时代,每个进程都可以在其完美的环境中启动,而不需要开销。好的。

    关于hashing和len()行为:好的。

    它告诉您,即使使用修改过的def.enc,您仍然不能忽略程序中处理的字符串类型。u""和""是内存中不同的字节序列-不总是这样,但通常是这样。好的。

    因此,在测试时,请确保您的程序在使用非ASCII数据时也能正确运行。好的。

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

    我个人并不认同这一点,因为散列行为与不改变散列行为一样。还没有看到一个令人信服的例子,说明由于我"拥有"的过程中的这种设置而产生的不受欢迎的行为。好的。

    总而言之,关于setdefaultencoding("utf-8"):关于其哑与否的答案应该更加平衡。好的。

    这要看情况而定。虽然它确实避免了崩溃,例如在一条日志语句中的str()操作,但代价是以后出现意外结果的可能性更高,因为错误的类型会使代码的正确运行时间更长,这取决于某个类型。好的。

    在任何情况下,对于您自己的代码,它都不应该是学习字节字符串和Unicode字符串之间区别的替代方法。好的。

    最后,将默认编码设置为不使用ASCII不会使普通文本操作(如len()、切片和比较)变得更容易-如果假设(byte)字符串使用utf-8进行所有操作可以解决这里的问题。好的。

    不幸的是,事实并非如此——总的来说。好的。

    "=="和len()结果比人们想象的要复杂得多,但即使在两边都有相同的类型。好的。

    w/o def.enc.changed,"=="对于非ASCII总是失败,如表中所示。有了它,它就工作了——有时:好的。

    Unicode确实标准化了世界上大约一百万个符号,并给了它们一个数字——但不幸的是,在输出设备中向用户显示的字形和它们所生成的符号之间没有1:1的双射。好的。

    为了激励你研究这一点:有两个文件,j1,j2,用相同的编码用相同的程序编写,包含用户输入:好的。

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

    结果:2.7.9 Jos_Jose?假(!)好的。

    在py2中使用print作为函数,您可以看到原因:不幸的是,有两种方法可以对同一个字符进行编码,即重音"e":好的。

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

    你可能会说一个多么愚蠢的编解码器,但它不是编解码器的错。这是Unicode中的一个问题。好的。

    所以即使在PY3中:好的。

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

    结果:3.4.2 Jos_Jose?假(!)好的。

    =>独立于PY2和PY3,实际上独立于您使用的任何计算语言:要编写高质量的软件,您可能必须"规范化"所有用户输入。Unicode标准确实标准化了标准化。在python 2和3中,unicodedata.normalize函数是您的朋友。好的。好啊。


    实词示例1

    它在单元测试中不起作用。

    测试运行程序(nosepy.test…)首先初始化sys,然后才发现并导入模块。到那时更改默认编码已经太迟了。

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

    是的,混合使用strunicode,依靠隐式转换,只会把问题进一步推下去。


    我们应该知道的一件事是

    Python 2 use sys.getdefaultencoding() to decode/encode between str and unicode

    conversion between str and unicode

    因此,如果我们更改默认编码,就会出现各种不兼容的问题。如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # coding: utf-8
    import sys

    print"你好" == u"你好"
    # False

    reload(sys)
    sys.setdefaultencoding("utf-8")

    print"你好" == u"你好"
    # True

    更多的例子:

    • https://pythonhosted.org/kitchen/unicode-fruminations.html网站

    也就是说,我记得有一些博客建议尽可能使用Unicode,在处理I/O时只使用位字符串。我认为如果你遵循这个惯例,生活会容易得多。可以找到更多解决方案:

    • https://pythonhosted.org/kitchen/unicode fruminations.html一些解决方案