Python如何更改默认编码?

181

当我从控制台运行我的应用程序时,我经常遇到“无法编码”和“无法解码”的问题,涉及Python。但在Eclipse PyDev IDE中,字符编码默认设置为UTF-8,我没有问题。

我搜索了默认编码的设置,并且人们说Python在启动时删除了sys.setdefaultencoding函数,我们无法使用它。

那么有什么最佳解决方案呢?


1
请查看博客文章 *The Illusive setdefaultencoding*。 - djc
3
最好的解决方案是学会正确使用编码和解码,而不是使用hack方法。在Python2中做到这一点肯定是可以的,但需要牢记并始终使用自己的接口。我的经验表明,当你写的代码需要同时在Python2和Python3中工作时,这会变得非常棘手。 - Att Righ
14个回答

175

下面是一种更简单的方法(hack),可以让你重新获得从sys中删除的setdefaultencoding()函数:

import sys
# sys.setdefaultencoding() does not exist, here!
reload(sys)  # Reload does the trick!
sys.setdefaultencoding('UTF8')
(针对Python 3.4+的注意事项:reload()importlib 库中。)
然而,这并不是一个安全的操作:这明显是一种黑客行为,因为当Python开始运行时,sys.setdefaultencoding() 被有意地从 sys 中删除。重新启用它并更改默认编码可能会破坏依赖ASCII作为默认编码的代码(这些代码可能是由第三方提供的,通常无法修复或危险)。
PS:这种黑客方法似乎在Python 3.9上不再起作用。

11
我给这个回答点了踩,因为它对于运行现有应用程序没有帮助(这是一种理解问题的方式),在编写/维护应用程序时是错误的,并且在编写库时会有危险。正确的方法是设置LC_CTYPE(或在应用程序中检查是否设置正确并使用有意义的错误消息终止)。 - ibotty
1
好的,它一开始并没有提到这是一个hack。除此之外,那些没有提到它们是危险答案的回答是没有帮助的。 - ibotty
1
@EOL 你说得对。这确实会影响首选编码(在Python 2和3中):LC_CTYPE=C python -c 'import locale; print( locale.getpreferredencoding())' - ibotty
1
即使重新加载后,'sys' 没有 'setdefaultencoding' 属性。 - negstek
1
你使用的是哪个版本的Python?我使用的是Python 3.9.13,并且观察到相同的事情(所以我更新了这个旧回答)。 - Eric O. Lebigot
显示剩余8条评论

98
如果在尝试管道/重定向脚本输出时出现以下错误: UnicodeEncodeError:'ascii'编解码器无法对位置0-5的字符进行编码:范围不在128范围内
只需在控制台中导出PYTHONIOENCODING,然后运行代码即可。
export PYTHONIOENCODING=utf8

3
这是唯一对我有效的解决方案。我使用的是Debian 7,但本地设置已损坏。谢谢。 - Pryo
4
LC_CTYPE设置为合适的值,这样做不仅可以使该程序更加稳定,还能让其他程序也变得更加顺畅。 - ibotty
7
Python3 中一个更大的 bug 是,PYTHONIOENCODING=utf8 不是默认设置。这会导致脚本因为 LC_ALL=C 而崩溃。 - Tino
只是想说,我在使用纯Python 2.7时没有任何问题。只有在使用__future__的Python 2.7时才会出现问题,所以我不得不使用这些解决方案(PYTHONIOENCODING或sys.reload)。在Python 3上也没有问题。 - Eric H.
请注意,Heroku Dynos 的默认编码设置为非 utf8,这会导致令人沮丧的调试体验(咳咳4个小时,直到我找到了这个答案...)。这将解决在 Heroku 上的问题。 - The Aelfinn
显示剩余3条评论

53

A) 控制 sys.getdefaultencoding() 的输出:

python -c 'import sys; print(sys.getdefaultencoding())'

ASCII

然后

echo "import sys; sys.setdefaultencoding('utf-16-be')" > sitecustomize.py

并且。
PYTHONPATH=".:$PYTHONPATH" python -c 'import sys; print(sys.getdefaultencoding())'

utf-16-be

utf-16-be是一种Unicode编码方式,它使用big-endian字节序来表示字符。

你可以将你的sitecustomize.py放在PYTHONPATH更高的位置。

此外,您也可以尝试@EOL提供的reload(sys).setdefaultencoding

B) 要控制stdin.encodingstdout.encoding,您需要设置PYTHONIOENCODING

python -c 'import sys; print(sys.stdin.encoding, sys.stdout.encoding)'

ASCII ASCII

然后

PYTHONIOENCODING="utf-16-be" python -c 'import sys; 
print(sys.stdin.encoding, sys.stdout.encoding)'

utf-16-be utf-16-be

这是关于编码格式的内容,具体来说是UTF-16BE编码格式。它是Unicode字符集的一种表示方式,其中“BE”代表big-endian字节序。这意味着高位字节先出现在内存中。如果你需要使用该编码格式,可以选择A或B或同时使用。

Finally: 最后,你可以选择使用A或B或同时使用。


独立但有趣的是,可以通过添加from __future__ import unicode_literals来扩展上述内容。请参见讨论 - lukmdo

18

PyDev 3.4.1开始,不再更改默认编码。 有关详细信息,请参见此票证

对于早期版本,解决方案是确保PyDev不以UTF-8作为默认编码运行。在Eclipse下,运行对话框设置(如果我记得正确的话就是“运行配置”);您可以在常规选项卡上选择默认编码。如果要在PyDev环境中尽早发现这些错误,请将其更改为US-ASCII。还请参见此解决方法的原始博客文章


1
谢谢Chris。特别是考虑到上面Mark T的评论,你的答案对我来说似乎是最合适的。对于一个不是主要使用Eclipse/PyDev的用户来说,我自己永远也想不出来。 - Sean
我想全局更改这个(而不是每次运行配置都更改),但还没有弄清楚如何操作 - 已经在另一个问题中提问:https://dev59.com/a2DVa4cB1Zd3GeqPgLkN - Tim Diggins

13

关于Python2(仅限Python2),一些之前的答案依赖于使用以下技巧:

import sys
reload(sys)  # Reload is a hack
sys.setdefaultencoding('UTF8')

使用sys.setdefaultencoding()是不被建议的(请参考此链接此链接)。

在我的情况下,这样做会带来一个副作用:我正在使用ipython笔记本,在运行代码后,“print”函数将不再起作用。我想可能会有解决办法,但我仍然认为使用这个技巧不应该是正确的选项。

尝试了许多选项后,对我有效的是sitecustomize.py中使用相同的代码,因为那段代码的目的就是如此。在评估了该模块之后,将从sys中删除setdefaultencoding函数。

因此,解决方案是将代码附加到文件/usr/lib/python2.7/sitecustomize.py

import sys
sys.setdefaultencoding('UTF8')

当我使用virtualenvwrapper时,我编辑的文件是~/.virtualenvs/venv-name/lib/python2.7/sitecustomize.py

而当我使用python notebooks和conda时,它是~/anaconda2/lib/python2.7/sitecustomize.py


8
有一篇关于这个的深入博客文章。请参见https://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/。以下是我对它的内容的概括。在 Python 2 中,由于字符串编码不够强制,您可以对编码不同的字符串执行操作,并成功完成。例如,下面的操作将返回True
u'Toshio' == 'Toshio'

这适用于使用 sys.getdefaultencoding() 编码为ascii 的每个(正常的,未加前缀的)字符串,但对其他字符串则不适用。默认编码应在 site.py 中全局更改,而不是在其他地方更改。将其设置为用户模块中的类似技巧只是一种临时解决方案,而不是根本解决方案。Python 3已将系统编码更改为默认为utf-8(当LC_CTYPE支持Unicode时),但必须明确将“字节”字符串编码为Unicode字符串后,才能使用它们。

6
这是我使用的方法,可以生成与python2和python3兼容并始终产生utf8输出的代码。我在别处找到了这个答案,但我记不起来源了。
此方法通过将sys.stdout替换为某些与文件类似但不完全相同的东西(仅使用标准库中的内容)。这可能会对底层库造成问题,但在您通过框架完全控制如何使用sys.stdout的简单情况下,这可以是一种合理的方法。
sys.stdout = io.open(sys.stdout.fileno(), 'w', encoding='utf8')

5

首先,reload(sys)并仅考虑输出终端流的随意设置一些默认编码是不好的做法。 reload 经常会更改依赖于环境的 sys.stdin/stdout 流、sys.excepthook 等在内的 sys 中的内容。

解决 stdout 的编码问题

我所知道的解决在 sys.stdout 上打印 unicode 字符串和超出 ASCII 范围的 str(例如从字面上)的编码问题的最佳方法是:关注一个能够满足需要且可选地容忍的 sys.stdout(类似文件对象):

  • 当某种原因导致 sys.stdout.encodingNone,或不存在,或错误地为 false 或比 stdout 终端或流实际能力“少”时,则尝试提供正确的 .encoding 属性。最后通过替换 sys.stdout & sys.stderr 来实现翻译文件对象。

  • 当终端/流仍无法对所有出现的 unicode 字符进行编码,并且您不希望仅因此而破坏 print,则可以在翻译文件对象中引入替换编码行为。

以下是一个示例:

#!/usr/bin/env python
# encoding: utf-8
import sys

class SmartStdout:
    def __init__(self, encoding=None, org_stdout=None):
        if org_stdout is None:
            org_stdout = getattr(sys.stdout, 'org_stdout', sys.stdout)
        self.org_stdout = org_stdout
        self.encoding = encoding or \
                        getattr(org_stdout, 'encoding', None) or 'utf-8'
    def write(self, s):
        self.org_stdout.write(s.encode(self.encoding, 'backslashreplace'))
    def __getattr__(self, name):
        return getattr(self.org_stdout, name)

if __name__ == '__main__':
    if sys.stdout.isatty():
        sys.stdout = sys.stderr = SmartStdout()

    us = u'aouäöüфżß²'
    print us
    sys.stdout.flush()

在Python 2/2+3代码中使用超出ASCII的纯字符串字面值

我认为唯一一个改变全局默认编码(仅限于UTF-8)的好理由是出于应用程序源代码决策,而不是因为I/O流编码问题:为了将超出ASCII的字符串字面值写入代码,而不必强制使用u'string'样式的Unicode转义。这可以通过处理Python 2或Python 2+3源代码基础来实现一致性(尽管anonbadger的文章所说的不同),该基础使用ASCII或UTF-8纯字符串字面值 - 只要这些字符串可能会经历静默的Unicode转换并在模块之间移动或潜在地进入stdout。为此,请优先选择"# encoding: utf-8"或ASCII(无声明)。更改或删除那些仍然以非常愚蠢的方式致命地依赖ASCII默认编码错误的库,超出了chr #127(这在今天很少见)。

并且在应用程序启动时(和/或通过sitecustomize.py)执行以下操作,除了上述的SmartStdout方案 - 不使用reload(sys)

...
def set_defaultencoding_globally(encoding='utf-8'):
    assert sys.getdefaultencoding() in ('ascii', 'mbcs', encoding)
    import imp
    _sys_org = imp.load_dynamic('_sys_org', 'sys')
    _sys_org.setdefaultencoding(encoding)

if __name__ == '__main__':
    sys.stdout = sys.stderr = SmartStdout()
    set_defaultencoding_globally('utf-8') 
    s = 'aouäöüфżß²'
    print s

这样,字符串字面量和大多数操作(除了字符迭代)在不考虑Unicode转换的情况下就可以轻松使用,就好像只存在Python3一样。

当然,与Python3一样,文件I/O始终需要特别注意编码。

注意:普通字符串在SmartStdout中被隐式地从utf-8转换为Unicode,然后再转换为输出流编码。


2
这是一个快速的技巧,适用于以下条件:(1)使用Windows平台;(2)运行Python 2.7;(3)因为一个好用的软件(即非你编写的软件,因此不能立即采用编码/解码打印技巧)无法在IDLE环境中显示“漂亮的Unicode字符”而感到困扰。例如,Stephan Boyer在他的教学证明器的输出中使用的整洁的一阶逻辑符号,该证明器位于First Order Logic Prover
我不喜欢强制进行系统重新加载,也无法让系统配合设置环境变量,如PYTHONIOENCODING(尝试直接设置Windows环境变量以及将其作为一个一行代码 ='utf-8' 放入 site-packages 中的 sitecustomize.py)。
因此,如果您愿意通过黑客方式取得成功,请转到您的IDLE目录,通常为: “C:\Python27\Lib\idlelib” 找到 IOBinding.py 文件。复制该文件并将其存储在其他地方,以便在需要时可以恢复到原始行为。使用编辑器(如IDLE)打开 idlelib 中的文件。转到此代码区域:
# Encoding for file names
filesystemencoding = sys.getfilesystemencoding()

encoding = "ascii"
if sys.platform == 'win32':
    # On Windows, we could use "mbcs". However, to give the user
    # a portable encoding name, we need to find the code page 
    try:
        # --> 6/5/17 hack to force IDLE to display utf-8 rather than cp1252
        # --> encoding = locale.getdefaultlocale()[1]
        encoding = 'utf-8'
        codecs.lookup(encoding)
    except LookupError:
        pass

换句话说,将跟在“try”后面的原始代码行注释掉,该代码行使编码变量等于“locale.getdefaultlocale”(因为这会给你cp1252,而你不想要),而是强制将其设置为“utf-8”(如所示添加行“encoding = 'utf-8'”)。
我相信这只影响IDLE向标准输出显示的编码,而不是用于文件名等的编码(该编码在之前通过filesystemencoding获得)。如果您以后在IDLE中运行任何其他代码时遇到问题,请使用原始未修改的IOBinding.py文件替换它。

1

设置 Windows 环境变量 PYTHONUTF8=1


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接