【问题标题】:Why is 'Â' printed in front of '±' when code is run?为什么运行代码时在“±”前面打印“”?
【发布时间】:2019-11-25 10:46:37
【问题描述】:

我正在尝试编写一个非常简单的输出语句,该语句输出到 csv 文件中。它只是说明了数据的偏差范围,所以我使用了“±”符号,所以它会读取类似“5 ft/s^2 ±2.4%”的内容。

我正在使用 Python3。我尝试了三种使用“±”符号的不同方法:ascii、unicode,以及直接将字符复制粘贴到编辑器中。见下文

val1 = 3.2
val2 = 2.4

s1 = val1 + "ft/sec^2 " + chr(241) + val2 + "%"
s2 = val1 + "ft/sec^2 " +  u'\u00B1' + val2 + "%"
s3 = val1 + "ft/sec^2 ±" + val2 + "%"

但是这三种方法的输出对我来说总是一样的......

3.2ft/sec^2 ±2.4%

这个 'Â' 继续出现。我对编码和类似的事情一点经验都没有。我搜索并发现了一些似乎与我有关但理解不够的情况,无法为我的具体情况拼凑出一个解决方案。

我使用 pandas DataFrame 来收集数据,然后使用 .to_csv() 方法创建 csv。它的文档说明它默认为“utf-8”编码。

这里有 7 行为我重现了同样的问题。

import pandas as pd 

df = pd.DataFrame(columns=['TestCol'])
df['TestCol'] = ["Test1: " + chr(241),
    "Test2: " + u'\u00B1',
    "Test3: " + "±"]
df.to_csv('TestExample.csv', index=False, encoding='utf-8')

在我的 CSV 中,我得到一个如下所示的列:

TestCol
Test1: ñ
Test2: ±
Test3: ±

感谢任何帮助、解释和知识!

【问题讨论】:

  • 后两个对我来说很好,第一个输出 ñ 而不是 ±。您可以编辑您的帖子以包含您用于写入 CSV 的代码吗?
  • Unicode 代码点 U+00A0 到 U+00BF 的 UTF-8 编码的第二个字节恰好与代码点本身重合。您的 UTF-8 编码值被解释为 ISO-8859。
  • 码位为241(十进制)的字符确实是ñ
  • @mzjn 出现了。当我刚刚将 chr(241) 插入 python 终端时,它会吐出——但同样的事情确实在创建 CSV 的代码中给了我,这让我感到困惑。
  • @MichaelKolber 我更新了帖子以包含创建我使用的 CSV 的方法

标签: python string unicode ascii


【解决方案1】:

Excel 在打开 .csv 文件时采用 Windows 编码。这种编码取决于语言/国家,但在英语和西欧国家,它是cp-1252,与ISO-8859-1(也称为“latin1”)非常相似。

这种编码对每个字符使用一个字节。这意味着它最多允许 256 个不同的字符(实际上,它们少于 256 个,因为某些代码是为控制字符和不可打印字符保留的)。

Python3 使用Unicode 来表示字符串。 Unicode 没有“只有 256 个”符号的限制,因为它在内部使用约 20 位。在实践中,Unicode 可以表示世界上任何语言的任何字符(甚至是世界上的某些语言)。

问题在于,当必须将 Unicode 写入文件(或通过网络传输)时,必须将其“编码”为字节序列。执行此操作的方法之一以及许多领域的当前标准是“UTF-8”。

UTF-8 编码使用每个字符的可变字节数。它被设计为与 ASCII 兼容,因此 ASCII 表中的任何符号都用一个字节表示(与它的 ascii 代码一致)。但是任何不在 ascii 中的字符都需要超过 1 个字节来表示。特别是,字符 ±(代码点 U+00B1 或 177)在以 UTF-8 编码时,需要两个字节的十六进制值 c2b1

当 Excel 读取这些字节时,由于它假定 cp-1252 编码每个字符使用一个字节,它会将序列 c2b1 解码为两个单独的字符。第一个解码为Â,第二个随便解码为±

注意顺便说一句,unicode ñ(代码点 U+00F1,或 241)在 UTF-8 中也被编码为两个字节,值 c3b1,解码时因为 cp-1252 显示为 ñ。请注意,第一个现在是Ã,而不是Â,但第二个又是(偶然又是)±

解决办法是给pandas指明写文件的时候应该使用cp-1252编码:

df.to_csv("file.csv", encoding="cp1252")

当然,这有一个潜在的问题。由于“cp-1252”最多只能表示 256 个符号,而 Unicode 可以表示超过 1M 的符号,因此数据框中的某些字符串数据可能会使用“cp-1252”中无法表示的任何字符。在这种情况下,您将收到编码错误。

另外,当用 Pandas 读回这个 .csv 时,您必须指定编码,因为 Pandas 假定它是 UTF-8。

关于utf-8-sig的更新

其他答案和一些 cmets 指的是"utf-8-sig" 编码,这将是另一种有效(也许更可取)的解决方案。我会详细说明这是什么。

UTF8 并不是将 Unicode 转换为字节序列的唯一方法,尽管它是多个标准中推荐的方法。另一个流行的选择是(曾经?)UTF-16。在这种编码中,所有 Unicode 字符都被编码为 16 位值(其中一些无法以这种方式表示,但可以通过对某些字符使用 两个 16 位值来扩展该集合)。

每个字符使用 16 位而不是 8 位的问题在于 endianess 是相关的。由于 16 位不是内存、网络和磁盘运行的基本单位,因此当您向内存、网络或磁盘写入或发送 16 位值时,实际上发送了两个字节。发送这些字节的顺序取决于架构。例如,假设您需要在磁盘中写入 16 位数字66ff(以十六进制表示)。您必须将其“分解”为66ff,并决定哪个先写入。磁盘中的序列可以是66ff(这称为大端顺序)或ff66(这称为小端顺序)。

如果您使用 little-endian 架构,例如 Intel,则磁盘中字节的默认顺序将不同于 big-endian 架构.当然,问题是当您尝试在架构与创建文件的机器不同的机器中读取文件时。您最终可能会将这些字节错误地组合为 ff66,这将是一个不同的 Unicode 字符。

因此必须通过某种方式在文件中包含有关创建时使用的字节序的信息。这就是所谓的 BOM(字节顺序标记)的作用。它包含 Unicode 字符FEFF。如果这个字符被写为文件中的第一个字符,当文件被回读时,如果你的软件发现FEFF作为第一个字符,它就会知道用来读取文件的字节序是与编写时使用的相同。但是,如果它找到FFFE(交换顺序),它将知道存在 endianity 不匹配,然后它将在读取时交换每对字节,以获取正确的 Unicode 字符。

顺便说一下,Unicode 标准没有代码为FFFE 的字符,以避免在读取 BOM 时混淆。如果在开头找到FFFE,说明字节序不对,必须交换字节。

这些都与 UTF-8 无关,因为这种编码使用字节(而不是 16 位)作为信息的基本单位,因此它不受字节顺序问题的影响。不过,您可以将 FEFF 编码为 UTF-8(它将生成 3 个字节的序列,其值为 EFBBBF)并将其写入文件中的第一个字符。这就是 Python 在您指定 utf-8-sig 编码时所做的事情。

在这种情况下,它的目的不是帮助确定字节顺序,而是充当一种“指纹”,帮助读回文件的软件猜测使用的编码是 UTF-8。如果软件发现文件中的前 3 个字节为“魔术值”EFBBBF,则可以断定文件以 UTF-8 存储。这三个字节被丢弃,其余的从 UTF-8 解码。

特别是,Microsoft Windows 在其大部分软件中都使用了这种技术。显然,在 Excel 的情况下,这也有效,所以总结一下:

  • 您使用df.to_csv("file.csv", encoding="utf-8-sig") 编写您的csv
  • Excel 读取文件并在开头找到EFBBBF。因此它会丢弃这些字节,并为文件的其余部分假定 utf-8。
  • 当后来序列c2,b1出现在文件中时,正确解码为UTF-8生成±

这具有在任何 Windows 计算机上工作的优势,无论它使用什么代码页(cp1252 用于西欧,其他国家可以使用其他代码页,但 Unicode 和 UTF-8 是通用的)。

潜在的问题是如果您尝试在非 Windows 机器上读取此 csv。第一个“魔术字节”EFBBBF 可能对读取它的软件毫无意义。然后,您可能会在文件开头以“虚假”字符结尾,这可能会导致问题。如果读取文件的软件采用 UTF-8 编码,则这三个前字节将被解码为 Unicode 字符FFFE,但它们不会被丢弃。这个字符是不可见的并且宽度为零,因此使用任何编辑器都无法“看到”它,但它仍然存在。如果读取文件的软件采用任何其他编码,例如“latin1”,这三个前字节将被错误地解码为,并且它们将在文件开头可见。

如果你使用python读回这个文件,你必须再次指定utf-8-sig编码,让python丢弃这三个初始字节。

【讨论】:

  • 太棒了,感谢您的解释和解决方案。我需要注意的是,“cp-1252”对我来说是一个错误,显然它需要在传递给这个方法时写成“cp1252”。再次感谢!
  • @the_pied_shadow 我在答案中解决了这个问题。感谢您的关注
  • @JLDiaz Excel 将在 utf-8-sig 用作编码时读取 UTF-8。这对 Excel 识别为 UTF-8 的签名进行编码。
  • @MarkTolonen 感谢您注意到这一点。我扩展了答案以解释这种编码的工作原理和一些注意事项。
【解决方案2】:

s3 包含一个 UTF8 编码的值,其中 ± (U+00B1) 的 UTF8 编码为\xc2\xb1。但是,您的终端将字节解释为 ISO-8859 编码的文本,而不是 UTF-8 编码的文本。在 ISO-8859 中,代码点 C2 是(您现在可能已经猜到了)“”,代码点 B1 是“±”。事实上,对于 U+00A0 和 U+00BF 之间的所有 Unicode 值,其 UTF-8 编码的第二个字节与其 Unicode 代码点一致。此外,ISO-8859-1 与代码点 00-FF 的 Unicode 一致。

【讨论】:

    【解决方案3】:

    您正在将 UTF-8 写入文件,但无论您使用什么来查看它,都将其视为 latin-1(或类似的 Windows cp1252)。您可以尝试使用encoding='utf-8-sig' open 将您正在写入的文件添加到文件的开头,这样应用程序就可以将其识别为UTF-8。或者您可能只是告诉您的查看器程序将其解释为 UTF-8。我强烈建议不要将其写成latin-1 或类似名称,因为这样会使文本无法移植到具有其他语言环境的系统中,而无需明确告诉人们如何对其进行解码。

    【讨论】:

    • 感谢您的回复。我没有打开要写入的文件,而是使用 .to_csv() 方法从 Pandas 数据帧创建文件。查看文档,它默认为 utf-8 进行编码,但我尝试明确说明它并没有改变任何东西。代码完成后我使用excel打开文件查看。
    • @the_pied_shadow:这就是为什么我特别建议utf-8-sig,而不仅仅是utf-8。编写 UTF-8 BOM 告诉 Windows 程序将字节解释为 UTF-8,而不是 cp1252(或它默认的任何其他可怕的语言环境特定编码)。如果您不使用open,您仍然应该能够使用您用于写入文件的任何内容指定编码;请在使用 cp1252 之前尝试一下,也就是“将烧毁西欧语言国家以外的任何人或使用您的数据的非 Windows 系统上的任何人的编解码器”。
    猜你喜欢
    • 2014-07-20
    • 2014-04-08
    • 1970-01-01
    • 2021-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-26
    相关资源
    最近更新 更多