代码运行时为什么在“±”前面打印“”?

the*_*dow 4 python string unicode ascii

我正在尝试编写一个非常简单的输出语句,该语句输出到一个 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 + "%"
Run Code Online (Sandbox Code Playgroud)

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

3.2ft/sec^2 ±2.4%
Run Code Online (Sandbox Code Playgroud)

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

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

这是为我重现相同问题的 7 行。

3.2ft/sec^2 ±2.4%
Run Code Online (Sandbox Code Playgroud)

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

TestCol
Test1: ñ
Test2: ±
Test3: ±
Run Code Online (Sandbox Code Playgroud)

任何帮助,解释和知识表示赞赏!

JLD*_*iaz 6

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 ñ(codepointU+00F1或 241)在 UTF-8 中也被编码为两个字节,值c3, b1,当解码为 cp-1252 时显示为ñ。请注意,第一个是 nowÃ而不是Â,但第二个是 again (casually again) ±

解决方法是向pandas表明写入文件时应该使用cp-1252编码:

df.to_csv("file.csv", encoding="cp1252")
Run Code Online (Sandbox Code Playgroud)

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

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

更新关于 utf-8-sig

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

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

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

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

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

顺便说一下,Unicode 标准没有一个字符的代码是FFFE,以避免在阅读 BOM 时混淆。如果FFFE在开头发现,则说明字节序错误,必须交换字节。

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

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

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

  • 你写你的csv使用 df.to_csv("file.csv", encoding="utf-8-sig")
  • Excel读取该文件,并认定EFBBBF在开始。因此它会丢弃这些字节并为文件的其余部分假定 utf-8。
  • 当squence c2,b1出现在文件中时,它被正确解码为UTF-8以产生±

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

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

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