为啥我们不应该在 py 脚本中使用 sys.setdefaultencoding("utf-8") ?

Posted

技术标签:

【中文标题】为啥我们不应该在 py 脚本中使用 sys.setdefaultencoding("utf-8") ?【英文标题】:Why should we NOT use sys.setdefaultencoding("utf-8") in a py script?为什么我们不应该在 py 脚本中使用 sys.setdefaultencoding("utf-8") ? 【发布时间】:2011-04-19 05:28:15 【问题描述】:

我见过一些在脚本顶部使用它的 py 脚本。在什么情况下应该使用它?

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

【问题讨论】:

在 ipython 中使用这个有问题,%time 停止工作github.com/ipython/ipython/issues/8071 @seanv507,阅读答案 - 严重不鼓励使用它 相关:Dangers of sys.setdefaultencoding('utf-8') 这不是Dangers of sys.setdefaultencoding('utf-8') 的完全复制品吗?虽然这个(2010)问早于那个(2015)?但这个问题也有很好的答案。该怎么办?另外,需要明确的是,这个问题仅在 Python 2 而非 3 上有意义,但没有任何标记或提及。 在深入了解 SO 答案之前值得一读:pythonhosted.org/kitchen/unicode-frustrations.html 【参考方案1】:

根据文档:这允许您从默认 ASCII 切换到其他编码,例如 UTF-8,Python 运行时将在必须将字符串缓冲区解码为 un​​icode 时使用该编码。

此功能仅在 Python 启动时可用,此时 Python 扫描环境。它必须在系统范围的模块sitecustomize.py 中调用。在评估此模块后,setdefaultencoding() 函数将从sys 模块中删除。

真正使用它的唯一方法是使用重新加载技巧来恢复属性。

另外,一直不鼓励使用sys.setdefaultencoding(),它已经成为 py3k 中的空操作。 py3k 的编码是硬连线到“utf-8”,更改它会引发错误。

我建议一些阅读指南:

http://blog.ianbicking.org/illusive-setdefaultencoding.html http://nedbatchelder.com/blog/200401/printing_unicode_from_python.html http://www.diveintopython3.net/strings.html#one-ring-to-rule-them-all http://boodebr.org/main/python/all-about-python-and-unicode http://blog.notdot.net/2010/07/Getting-unicode-right-in-Python

【讨论】:

很棒的东西,虽然这里的信息太多会有点死。我学到最多的只是关注这篇文章:blog.notdot.net/2010/07/Getting-unicode-right-in-Python 我想补充一点,默认编码也用于 encoding (当写入sys.stdout 时,当它具有None 编码时,例如重定向输出时Python 程序)。 +1 for “一直不鼓励使用sys.setdefaultencoding() '硬连线到 utf-8' 不是真的,它不是硬连线的,它并不总是UTF-8LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'UTF-8LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'ANSI_X3.4-1968(或者可能是别的) @Tino,控制台编码与默认编码是分开的。【参考方案2】:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
u = u'moçambique'
print u.encode("utf-8")
print u

chmod +x test.py
./test.py
moçambique
moçambique

./test.py > output.txt
Traceback (most recent call last):
  File "./test.py", line 5, in <module>
    print u
UnicodeEncodeError: 'ascii' codec can't encode character 
u'\xe7' in position 2: ordinal not in range(128)

在 shell 上工作,发送到 sdtout 不, 所以这是一种解决方法,写入标准输出。

我做了其他方法,如果未定义 sys.stdout.encoding 则不会运行,或者换句话说,需要先导出 PYTHONIOENCODING=UTF-8 才能写入标准输出。

import sys
if (sys.stdout.encoding is None):            
    print >> sys.stderr, "please set python env PYTHONIOENCODING=UTF-8, example: export PYTHONIOENCODING=UTF-8, when write to stdout." 
    exit(1)

因此,使用相同的示例:

export PYTHONIOENCODING=UTF-8
./test.py > output.txt

会起作用

【讨论】:

这没有回答所问的问题。而是关于这个主题的一些切线想法。【参考方案3】:

tl;博士

答案是从不(除非你真的知道自己在做什么)

正确理解编码/解码可以解决9/10倍的解决方案。

1/10 人的语言环境或环境定义不正确,需要设置:

PYTHONIOENCODING="UTF-8"  

在他们的环境中修复控制台打印问题。

它有什么作用?

sys.setdefaultencoding("utf-8")(为了避免重复使用而删除)更改了 Python 2.x 需要将 Unicode() 转换为 str() 时使用的默认编码/解码(反之亦然)反之亦然)并且没有给出编码。即:

str(u"\u20AC")
unicode("€")
"".format(u"\u20AC") 

在 Python 2.x 中,默认编码设置为 ASCII,上面的示例将失败:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

(我的控制台配置为 UTF-8,所以"€" = '\xe2\x82\xac',因此\xe2 出现异常)

UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)

sys.setdefaultencoding("utf-8") 将允许这些为 me 工作,但不一定适用于不使用 UTF-8 的人。 ASCII 的默认设置确保不会将编码假设融入代码

控制台

sys.setdefaultencoding("utf-8") 还有一个副作用是似乎修复了sys.stdout.encoding,在将字符打印到控制台时使用。 Python 使用用户的语言环境 (Linux/OS X/Un*x) 或代码页 (Windows) 来设置它。有时,用户的区域设置被破坏,只需要PYTHONIOENCODING 来修复控制台编码

例子:

$ export LANG=en_GB.gibberish
$ python
>>> import sys
>>> sys.stdout.encoding
'ANSI_X3.4-1968'
>>> print u"\u20AC"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)
>>> exit()

$ PYTHONIOENCODING=UTF-8 python
>>> import sys
>>> sys.stdout.encoding
'UTF-8'
>>> print u"\u20AC"
€

sys.setdefaultencoding("utf-8")有什么不好?

人们已经针对 Python 2.x 进行了 16 年的开发,因为他们理解默认编码是 ASCII。 UnicodeError 已编写异常处理方法来处理发现包含非 ASCII 字符串的字符串到 Unicode 转换。

来自https://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/

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"))

在设置 defaultencoding 之前,此代码将无法解码 ascii 编码中的“Å”,然后会进入异常处理程序以猜测编码并将其正确转换为 unicode。印刷:Angstrom (Å®) 经营您的业务。将默认编码设置为 utf-8 后,代码会发现 byte_string 可以解释为 utf-8,因此它将破坏数据并返回:Angstrom (Ů) 运行您的业务。

更改应该是常量的值将对您所依赖的模块产生巨大影响。最好只修复进出代码的数据。

示例问题

虽然将 defaultencoding 设置为 UTF-8 不是以下示例中的根本原因,但它显示了问题是如何被掩盖的,以及当输入编码发生变化时,代码如何以一种不明显的方式中断: UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 3131: invalid start byte

【讨论】:

虽然sys.setdefaultencoding("utf-8") 有惊喜,但最好让代码表现得更像 Python 3。现在是 2017 年。即使您在 2015 年写下答案,我认为向前看比向后看已经更好。这对我来说实际上是最简单的解决方案,当我发现我的代码在 Python 2 中的行为取决于输出是否被重定向时(Python 2 非常讨厌的问题)。不用说,我已经有了# coding: utf-8,并且我不需要任何针对 Python 3 的解决方法(我实际上必须使用版本检查来屏蔽setdefaultencoding)。 这很好,它对你有用,但sys.setdefaultencoding("utf-8") 不会使你的 Py 2.x 代码与 Python 3 兼容。它也不会修复假定默认编码为 ASCII 的外部模块。使您的代码与 Python 3 兼容非常简单,不需要这种讨厌的 hack。例如,为什么这会导致非常实际的问题,请参阅我对亚马逊的经验与此假设的混淆:***.com/questions/39465220/… @AlastairMcCormack 你太棒了,我的网站已经有几个月了,不知道该怎么做。最后,PYTHONIOENCODING="UTF-8" 帮助了我的 Python2.7 Django-1.11 环境。谢谢。 我知道你复制了这个例子,但我可以找到 detect_encoding 的包。 @dlamblin 代码示例是为了证明引用,不应该在您的代码中使用。想象一下detect_encoding是一种可以根据语言线索检测字符串编码的方法。【参考方案4】:

第一个危险在于reload(sys)

当您重新加载一个模块时,您实际上会在运行时获得该模块的 两个 副本。旧模块和其他所有模块一样是一个 Python 对象,只要有对它的引用,它就会一直存在。因此,一半的对象将指向旧模块,一半指向新模块。当您进行一些更改时,当一些随机对象没有看到更改时,您将永远不会看到它的到来:

(This is IPython shell)

In [1]: import sys

In [2]: sys.stdout
Out[2]: <colorama.ansitowin32.StreamWrapper at 0x3a2aac8>

In [3]: reload(sys)
<module 'sys' (built-in)>

In [4]: sys.stdout
Out[4]: <open file '<stdout>', mode 'w' at 0x00000000022E20C0>

In [11]: import IPython.terminal

In [14]: IPython.terminal.interactiveshell.sys.stdout
Out[14]: <colorama.ansitowin32.StreamWrapper at 0x3a9aac8>

现在,sys.setdefaultencoding() 正确

All that it affects is implicit conversion str&lt;-&gt;unicode。现在,utf-8 是地球上最合理的编码(向后兼容 ASCII 和所有编码),现在转换“正常工作”,可能会出现什么问题?

好吧,随便。这就是危险。

可能有一些代码依赖于 UnicodeError 被抛出用于非 ASCII 输入,或者使用错误处理程序进行转码,现在会产生意外结果。而且由于所有代码都使用默认设置进行测试,因此您在此处严格处于“不受支持”的领域,没有人向您保证他们的代码将如何运行。 如果系统上并非所有内容都使用 UTF-8 because Python 2 actually has multiple independent "default string encodings",则转码可能会产生意外或不可用的结果。 (请记住,程序必须在客户的设备上为客户工作。) 同样,最糟糕的是你永远不会知道这一点,因为转换是隐式的——你真的不知道它发生的时间和地点。(Python Zen ,koan 2 ahoy!)您永远不会知道为什么(以及是否)您的代码在一个系统上工作并在另一个系统上中断。 (或者更好的是,在 IDE 中工作并在控制台中中断。)

【讨论】:

以上是关于为啥我们不应该在 py 脚本中使用 sys.setdefaultencoding("utf-8") ?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不应该在 javascript 中使用 ++? [复制]

为啥我们不应该在生产服务器上的 mysql 查询中使用 Select *?

我应该在 NPM package.json 中将我的“启动”脚本设置为啥?

为啥我应该使用 MailChimp 或类似工具而不是自定义脚本? [复制]

为啥我们需要复制构造函数以及何时应该在 java 中使用复制构造函数

使用 CloudFlare 301 重定向时,为啥脚本不呈现?