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

时间:2015-02-22 10:55:26

标签: python encoding utf-8 python-2.x

有一种趋势是阻止在Python 2中设置sys.setdefaultencoding('utf-8')。任何人都可以列出问题的真实例子吗?像it is harmfulit hides bugs这样的论点听起来不太令人信服。

更新:请注意,此问题仅与utf-8有关,而不是“一般情况下”更改默认编码。

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

4 个答案:

答案 0 :(得分: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

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

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

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

但是,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和字节串数据时,你真的需要知道,而不是用全局画笔对它进行抹灰并希望最好。

答案 1 :(得分:3)

首先:许多改变默认情况的反对者认为它的愚蠢是因为even changing ascii comparisons

我认为公平地表明,在符合原始问题的情况下,我认为没有人提倡除了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            |     
| (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数据。

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

如前所述:这是一个流程范围选项,因此您只需一次选择它 - 这就是为什么开发人员真的永远不会这样做的原因但是按顺序获取它们的内部结构,这样它们就不需要依赖python的隐式转换。 他们还需要清楚地记录他们期望和返回的内容并拒绝他们没有编写lib的输入(比如normalize函数,见下文)。

=>使用该设置编写程序会使其他人在代码中使用程序模块存在风险,至少不会过滤输入。

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


关于散列和len()行为:

它告诉你,即使修改了def.enc。你仍然不能忽视你在程序中处理的字符串类型。 u'和''是内存中不同的字节序列 - 并非总是如此。

因此,在测试时,请确保您的程序在非Ascii数据中也能正常运行。

有人说,当数据值发生变化时,散列可能变得不相等 - 尽管由于隐式转换,'=='操作保持不变 - 这是反对改变def.enc的论据。

我个人不同意,因为散列行为与不改变散列行为一样。还没有看到一个令人信服的例子,说明由于我自己的过程中的设置导致的不良行为。

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

这取决于。 虽然它可以避免崩溃,例如在日志语句中的str()操作中 - 由于错误的类型使得代码的正确功能取决于某种类型,错误的类型使其更长,因此价格在以后出现意外结果的可能性更高。

在任何情况下,它都不应该替代学习自己代码的字节字符串和unicode字符串之间的区别。


最后,将默认编码设置为远离Ascii并不会使你的生活变得更容易进行常见的文本操作,如len(),切片和比较 - 你应该假设(字节)使用UTF-8对解决问题的所有内容进行拼接。< / p>

不幸的是,它不是 - 一般而言。

'=='和len()结果是比人们想象的更复杂的问题 - 但即使双方都使用相同的类型。

W / o def.enc。更改,“==”总是对非Ascii失败,如表中所示。有了它,它有效 - 有时候:

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

激励你research this:使用包含用户输入的相同编码,使用相同的程序写入两个文件j1,j2:

>>> 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(!)

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

答案 2 :(得分:2)

真实单词示例#1

它不适用于单元测试

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

同样的优点,如果有人将您的代码作为模块运行,它就不起作用,因为它们的初始化首先出现。

是的,混合strunicode并依赖隐式转化只会将问题进一步推向一线。

答案 3 :(得分:1)

我们应该知道的一件事是

  

Python 2使用SELECT account_has_photos.photos_id as id, "photos" as type, account_has_photos.update_at, account_has_photos.account_id, posts.total FROM account_has_account1 JOIN account_has_photos ON (account_has_photos.account_id = account_has_account1.account_id1 AND account_has_photos.type_id = 17) INNER JOIN ( SELECT photos_has_message_photos.photos_id, count(photos_has_message_photos.photos_id) as total FROM photos_has_message_photos ) posts ON(posts.photos_id = account_has_photos.photos_id) WHERE account_has_account1.account_id = 7 AND account_has_account1.`status` = "Active" sys.getdefaultencoding()str之间解码/编码

conversion between str and unicode

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

unicode

更多例子:

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