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

ana*_*nik 33 python encoding utf-8 python-2.x

sys.setdefaultencoding('utf-8')在Python 2中存在令人沮丧的设置趋势.任何人都可以列出问题的真实例子吗?论证喜欢it is harmfulit hides bugs听起来不太令人信服.

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

如果可以,请举一些代码示例.

Red*_*ill 23

原始海报要求提供代码,证明交换机是有害的 - 除了它"隐藏"与交换机无关的错误.

结论摘要

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

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

  2. 对交换机的"官方"拒绝是基于绝大多数最终用户(不是图书馆提供商)不再相关的原因,因此我们应该停止阻止用户进行设置.

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

实际上,非常频繁地修改默认编码可以避免绝大多数用例中的许多用户头痛.是的,在某些情况下处理多种编码的程序会默默地行为不端,但由于这种切换可以零散启用,因此这不是最终用户代码中的问题.

更重要的是,启用此标志是一个真正的优势,是用户的代码,既可以减少必须手动处理Unicode转换的开销,使代码混乱并使其可读性降低,又可以避免程序员无法执行此操作时的潜在错误在所有情况下都适当.


由于这些声明几乎完全与Python的官方通信线路相反,我认为对这些结论的解释是有道理的.

在野外成功使用修改后的defaultencoding的示例

  1. Fedora的Dave Malcom认为它总是正确的.在调查风险之后,他建议改变所有 Fedora用户的分布范围def.enc.= UTF-8 .

    虽然提出了铁的事实,为什么 Python的将打破只有散列行为我上市,这是从来没有在核心社区内拾起任何其他对手的理由担心,甚至由同一个人,在用户票工作时.

    Fedora的简历:不可否认,变更本身被核心开发人员描述为"非常不受欢迎",并被指责与先前版本不一致.

  2. 独自3000个项目在openhub做.他们有一个慢搜索前端,但扫描它,我估计98%使用UTF-8.没有发现令人讨厌的惊喜.

  3. 18000(!)个github主分支,它改变了.

    虽然这种变化在核心社区" 不受欢迎 ",但在用户群中非常受欢迎.虽然这可以被忽视,但由于已知用户使用hacky解决方案,我认为这不是一个相关的论点,因为我的下一个观点.

  4. 由于这个原因,GitHub上只有150个bug报告.以实际100%的速度,变化似乎是积极的,而不是消极的.

    总结人的存在的问题碰上了,我已经通过上述所有门票扫描.

    • Chaging def.enc.UTF-8通常是在问题结束过程中引入但未删除的,通常作为解决方案.一些较大的人原谅它作为临时的解决办法,考虑到"坏消息"有,但更臭虫记者都只是很高兴关于修复.

    • 一些(1-5?)项目修改了他们的代码手动进行类型转换,因此他们不再需要更改默认值.

    • 在两个例子中,我看到有人声称使用def.enc.设置为UTF-8导致完全缺乏产量的 完全,没有解释的测试设置.我无法验证声明,我测试了一个并发现相反的情况属实.

    • 有人声称他的"系统"可能取决于不改变它,但我们不知道为什么.

    • 一个(且只有一个),有一个真正的理由,以避免它:IPython中或采用第三方模块或测试运行修改其过程以不受控制的方式(这是从来没有争议,一个def.enc变化是由它的主张.支持者只在翻译设置时,即"拥有"过程时).

  5. 我发现零指示"é"和"u'é"的不同哈希值会导致现实世界代码出现问题.

  6. Python 没有 "破解"

    将设置更改为UTF-8后,单元测试所涵盖的Python功能与​​没有交换机的情况完全不同.但是,开关本身根本没有经过测试.

  7. 在bugs.python.org上建议沮丧的用户

    此处,此处此处的示例 (通常与官方警告线相关)

    第一个演示了交换机在亚洲的建立程度(与github参数相比).

  8. Ian Bicking 发布了他对始终启用此行为的支持.

    我可以使我的系统和通信始终保持UTF-8,事情会变得更好.我真的没有看到一个缺点.但是为什么Python让它变得如此艰难[...]我觉得有人认为他们比我聪明,但我不确定我是否相信他们.

  9. Martijn Fassen在反驳Ian时承认,ASCII首先可能是错误的.

    我相信,如果说Python 2.5附带了UTF-8的默认编码,它实际上不会破坏任何东西.但如果我为我的Python做了这件事,我很快就会遇到问题,因为我把代码交给了其他人.

  10. 在Python3中,他们不"实践他们所宣扬的"

    反对任何def.enc.由于依赖于环境的代码或隐含性而变得非常严厉,这里的讨论围绕着Python3'unicode sandwich'范式和相应的必要隐含假设的问题.

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

    >>> from ????????? import *        
    >>> def ??(???): ???(?(???))
    >>> ??(???(' ') + ?)
    
    
    Run Code Online (Sandbox Code Playgroud)
  11. DiveIntoPython 推荐它.

  12. 在这个线程,圭多自己建议一个专业最终用户使用过程中的具体environt设置为开关"为每个项目创建一个自定义的Python环境."

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

  13. Jython提供即时在模块中即时更改它.

  14. PyPy 支持reload(sys) - 但是在没有问题的情况下在一天内将其恢复用户请求.与CPython 的" 你做错了 "的态度相比,声称没有证据就是"邪恶的根源".


结束此列表我确认可以构造一个因为更改的解释器配置而崩溃的模块,执行如下操作:

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>
Run Code Online (Sandbox Code Playgroud)

我不认为这是一个有效的论点,因为编写这种双类型接受模块的人显然知道ASCII与非ASCII字符串,并且会注意编码和解码.

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

  • 这个答案实在太长了,而且不必要.大部分的论据,会占用你的大部分职位的那些,似乎充其量仅此而已的[诉诸群众(https://en.wikipedia.org/wiki/Argumentum_ad_populum)和[证明最后的详细信息(https://en.wikipedia.org/wiki/Proof_by_intimidation).此外,关于标准化和编码的整个部分是无关紧要的,属于博客文章,而不是Stack Overflow的答案.如果您只是简单地提出*技术原因*,那么您的答案会更好. (7认同)
  • 这不应该是您在评论Martijn答案时链接到的博客条目吗? (4认同)
  • 一些特定的注释:设置不同的默认值就像使用`goto`.当然,你可以使它工作,但在开发应用程序时你会有更难的时间.你在处理Unicode方面会有所不一致,这会让你感到困惑.大多数使用它的人不会*理解Unicode,并认为这是一个简单的方法. (2认同)
  • 许多GitHub代码使用它的论据并不能证明它可以使用,它也可以作为证据,大多数开发人员不理解如何正确使用Unicode.你看到[缺乏经验的开发人员如何使用`super()`的相同问题(/sf/ask/1372569411/#19609168).一般来说,它是[*Cargo Cult*](http://en.wikipedia.org/wiki/Cargo_cult),在不理解*如何工作*或者根本不需要的情况下应用和误用. (2认同)

Mar*_*ers 16

因为您并不总是希望将字符串自动解码为Unicode,或者您的Unicode对象自动编码为字节.既然你要求一个具体的例子,这里有一个:

获取WSGI Web应用程序; 您正在通过将外部进程的产品添加到列表中,在循环中构建响应,并且该外部进程为您提供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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

你用英语对它进行了测试,一切仍然有效,太棒了!

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

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

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

如果你没有使用sys.setdefaultencoding()异常会被提出,你知道你有一个错误,但现在你的客户抱怨不完整的回复; 页面末尾缺少字节,你不太清楚这是怎么回事.

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

当您仅处理ASCII数据时,Python 2在其间strunicode类型之间进行编码和解码可能是有用且安全的.但是你真的需要知道什么时候你不小心混合了Unicode和字节串数据,而不是用全局画笔涂抹它并希望最好.

  • @techtonik:为什么Python处理多字节编码的错误?Unicode字符串的长度应该是代码点的数量,而不是任意编解码器中的字节数.字节串的长度应该是字节数.Content Length标头应包含字节数,而不是代码点数.我不明白为什么这是一个多字节与单字节编码问题. (5认同)
  • @techtonik:我们围成一圈.你不认为这很糟糕,因为你没有看到隐式转换类型是多么糟糕.在隐式转换是异常而不是默认转换的语言中,这是一个**巨大的**问题,并且您正在全局级别更改该转换的规则.如果这是为每个模块配置的话,你可以自由地射击自己,而不会强迫你使用任何第三方库的问题.但这不是这里的情况,如果你没有看到这种行为的问题,我不知道该告诉你什么. (4认同)
  • @techtonik:在这里使用Jinja2揭示了Roundup没有实践*Unicode三明治*方法; 尽可能早地在入口点的应用程序`unicode`中生成*all text*,并且只在出口点编码为字节,尽可能晚.在这种情况下,我建议阅读/观看Ned Batchelder的[实用Unicode演示](http://nedbatchelder.com/text/unipain.html). (3认同)
  • "你计算的长度是完全错误的"是一个很好的论据.http://pastebin.ubuntu.com/10791721/在控制台上给出了3和6.但这看起来像Python中的一个错误,它无法处理mutibyte编码. (2认同)

Dim*_*nek 2

真实例子#1

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

测试运行程序(nosepy.test、 ...)sys首先初始化,然后才发现并导入您的模块。到那时更改默认编码就为时已晚。

同样,如果有人将您的代码作为模块运行,它也不起作用,因为他们的初始化是第一位的。

是的,混合strunicode依赖隐式转换只会使问题进一步恶化。