python3 小写时 unicode 失败

python3 unicode fails when lowercasing

我遇到了一个非常奇怪的问题 Python 3:

>>> a = "abcé"
>>> a
'abcé'
>>> print(a)
abcé
>>> print(a.lower())
abc�

我不知道这是从哪里来的,但它无法将 unicode 字符小写。请注意,我无法在所有地方重现错误,这只是在我的一台计算机上出现以下问题。此外,python2 在同一台计算机上正确打印 abcé.

此外,a.upper() 返回的是 ABCé 而不是 ABCÉ,因此它不会遇到与 lower..

相同的问题

有什么想法吗?

我不完全确定这里发生了什么,但看起来 python 和您的终端之间存在一些编码问题。

无论如何,最佳做法是将所有内容都保留在 'utf-8' 中,以确保您可以在 python 中执行 import localelocale.setlocale(locale.LC_ALL, 'en_US.UTF-8') 或执行 export LC_ALL=en_US.utf8.

我不是 100% 精通这些编码主题,所以也许编码专家有更好的 answer/explanation 但这应该可以解决您的问题。

您观察到的行为确实很奇特:字母“é”不受Python的str.upper()的影响,并变成了str.lower()的替换字符。 同样奇怪的是,这似乎取决于环境,因为所述 str 方法没有表现出任何本地化(尽管有时这可以说是有意义的,例如 [=66 的土耳其映射的情况) =]→“İ”和“ı”→"I"),但始终使用 Unicode 的默认大小写算法。

可能的解释

对这种奇怪现象最可能的解释是 Python 与您 "see" 的数据不同。 正如 Hansaplast 在他们的回答中所写,终端和 Python 解释器之间可能存在编码不匹配。 人们通常不必关心这一点,但是当您使用交互式解释器时,显示键入和打印字符的工作实际上并不是由 Python 执行的,而是由终端 [emulator] 执行的,而这个额外的图层有时可能是问题的根源。

那么到底是怎么回事呢? 我相信以下场景可以解释观察到的行为:

  • 您的终端配置为使用 UTF-8。当您键入“é”时,它会将字节 C3 A9 发送到 Python。当它从Python接收到C3 A9时,它会显示“é”。
  • 然而,
  • Python 使用 Latin-1,正如您通过 locale.getlocale() 的 return 值确认的那样。当它收到 C3 A9 时,它会将其解码为“É”,这是 mojibake 的常见情况。
  • UTF-8 和 Latin-1 都是 ASCII 的超集,所以只要您只使用 ASCII 字符,这种错误配置就不是问题。当您键入 "A" 时,Python 读取 "A",输出也是如此。

这种错误配置的真正令人讨厌的地方在于它只在某些情况下可见。 由于 en-/decoding 的对称性,甚至非 ASCII 字符也可能不被注意地通过。 如果 Python 只是简单地回应它的输入,即。打印“É”,这将被终端去mojibaked成“é”,所以错误被隐藏了。 但是,当以某种方式解释单个字符时(如 str.upper()lower()),可能会发生意想不到的事情。

在您的情况下,.upper() 无效,因为“Ô已经大写,而“©”不区分大小写。这就是 'abcé'.upper() 在屏幕上显示 'ABCé' 的原因。 但是小写会产生“ã©”,Python 编码为 E3 A9。 由于这不是有效的 UTF-8 字节序列,终端无法解释它并显示替换字符 (�)。

解决方案

如果此解释属实,您如何解决编码错误配置问题?

  • 对于交互式会话,Python 使用诸如 LC_ALL 的环境变量来设置 STDIN/STDOUT 的编码可能是有意义的。在您的终端中运行的 shell 的启动脚本中放入类似 export LC_ALL=en_US.utf8 的行,例如。 .bashrc。在 Python 中更改语言环境没有任何效果,因为 STD 流编码是在启动时设置的,并且在您调用 locale.selocale().

  • 时不会更新
  • 对于脚本,您可能不想依赖环境变量。 您可以围绕每个标准通道下的二进制流创建一个新的 io.TextIOWrapper

    sys.stdin = open(sys.stdin.buffer.fileno(), encoding='utf8')
    sys.stdout = open(sys.stdout.buffer.fileno(), 'w', encoding='utf8')
    sys.stderr = open(sys.stderr.buffer.fileno(), 'w', encoding='utf8')
    

    (我不建议在交互式会话中使用此解决方案。特别是如果您输入错误的内容,您可能会陷入难以恢复的境地。)