IPython 终端使用哪种字符编码?

Which character encoding is the IPython terminal using?

我曾经认为我已经把整个编码的东西都弄清楚了。我似乎错了,因为我无法解释这里发生的事情。

我想做的是使用 tabulate 模块打印格式良好的 table 使用

from tabulate import tabulate
s = tabulate([[1,2],[3,4]], ["x","y"], tablefmt="fancy_grid")
print(s)

在IPython3.5.0的交互控制台下Windows10.我预计结果是

╒═════╤═════╕
│   x │   y │
╞═════╪═════╡
│   1 │   2 │
├─────┼─────┤
│   3 │   4 │
╘═════╧═════╛

但是,我得到了一个

UnicodeEncodeError: 'charmap' codec can't encode character '\u2552' in position 0: character maps to <undefined>

百思不得其解,想找出问题所在,查看了字符串的repr

In [15]: s
Out[15]: '╒═════╤═════╕\n│   x │   y │\n╞═════╪═════╡\n│   1 │   2 │\n├─────┼─────┤\n│   3 │   4 │\n╘═════╧═════╛'

嗯,终端可以显示所有字符(即使是第一个触发错误的字符)。

只是检查一些细节:

In [16]: sys.stdout.encoding
Out[16]: 'cp850'

In [17]: s.encode("cp850")
[...]
UnicodeEncodeError: 'charmap' codec can't encode character '\u2552' in position 0: character maps to <undefined>

那么终端使用的是哪种编码? Python 说它是 cp850,它告诉我 cp850 没有 字符(which is true,它是 cp437 必须为重音字母腾出空间),但我可以 在终端中看到 它 window!

更复杂的是,当使用本机 Python 控制台而不是 IPython 时,错误似乎更容易理解:

>>> s
'\u2552═══\u2564═══\u2555\n│ 1 │ 2 │\n├───┼───┤\n│ 3 │ 4 │\n\u2558═══\u2567═══\u255b'
>>> sys.stdout.encoding
'cp850'
>>> print(s)
Traceback (most recent call last):
[...]
UnicodeEncodeError: 'charmap' codec can't encode character '\u2552' in position 0: character maps to <undefined>

所以至少 Python 是一致的,但是 IPython 是怎么回事?

IPython 在交互模式下像任何其他 Python 控制台程序一样使用 OEM 代码页:

In [1]: '\u2552'
ERROR - failed to write data to stream: <_io.TextIOWrapper name='<stdout>' mode=
'w' encoding='cp850'>
Out[1]:

In [2]: !chcp
Active code page: 850

如果安装了 pyreadline,结果会发生变化(它会在 IPython 控制台中启用颜色等):

In [1]: '\u2552'
Out[1]: '╒'

In [2]: import sys

In [3]: sys.stdout.encoding
Out[3]: 'cp850'

In [4]: !chcp
Active code page: 850

安装 pyreadline 后,IPython 的 sys.displayhook 将结果写入使用 WriteConsoleW() Windows Unicode [=28] 的 readline 控制台对象=] 允许在当前代码页中打印甚至无法编码的 Unicode 字符(要查看它们,您可能需要在 Windows 控制台中配置 (TrueType) 字体,例如 Lucida Console)。