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中编码时,需要两个十六进制值c2和b1的字节。
当Excel读取这些字节时,由于它假定cp-1252编码使用每个字符一个字节,它将解码序列c2、b1作为两个单独的字符。第一个字符被解码为Â,第二个字符非正式地被解码为±。
注意:顺便提一下,Unicodeñ(代码点U+00F1或241)也被编码为两个字节,值为c3、b1,当以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”的更新:
其他答案和一些评论提到了“utf-8-sig”编码,这可能是另一个有效(也许更好)的解决方案。我稍微解释一下它是什么。
UTF8并不是将Unicode转换为字节序列的唯一方式,虽然它被几个标准推荐。另一种流行的选择是(曾经是?)UTF-16。在这种编码方式下,所有Unicode字符都被编码为16位值(一些字符无法用这种方式表示,但是可以通过使用两个16位值扩展集合来解决)。
使用每个字符16位而不是8位的问题在于,这时字节序成为了相关因素。由于16位不是内存、网络和磁盘操作的基本单位,因此当您将16位值写入或发送到内存、网络或磁盘时,实际上会发送两个字节。这些字节发送的顺序取决于架构。例如,假设您需要在磁盘中写入16位数字
66ff
(以十六进制表示)。您必须将其“分解”为
66
和
ff
,并决定先写哪一个。磁盘中的顺序可以是
66
,
ff
(这被称为大端序),或
ff
,
66
(这被称为小端序)。
如果您处于小端架构(例如英特尔),则磁盘中字节的默认顺序将与大端架构不同。当然,问题在于当您尝试在与创建文件的机器架构不同的机器上读取文件时。您可能会错误地组装这些字节为
ff66
,这将是不同的Unicode字符。
因此,必须有一种方法在文件中包含关于创建时使用的字节序的信息。这就是所谓的BOM(字节顺序标记)的作用。它由Unicode字符FEFF组成。如果将此字符写为文件中的第一个字符,则在读回文件时,如果您的软件发现FEFF作为第一个字符,它将知道读取文件时使用的字节序与编写文件时使用的字节序相同。但是,如果它找到FFFE(顺序被交换),则它将知道存在字节序不匹配,然后在读取时交换每对字节以获得正确的Unicode字符。
顺便说一下,Unicode标准没有其代码为FFFE的字符,以避免读取BOM时产生混淆。如果在开头找到FFFE,则意味着字节序错误,必须交换字节。
所有这些都与UTF-8无关,因为此编码使用字节(而不是16位)作为信息的基本单位,因此免疫字节序问题。尽管如此,您仍然可以在UTF-8中编码FEFF(它将导致一个由值为EF、BB和BF的3个字节序列),并将其写为文件中的第一个字符。这就是Python在指定utf-8-sig编码时所做的。
在这种情况下,它的目的不是帮助确定字节序,而是作为一种“指纹”,帮助读取文件的软件猜测使用的编码是UTF-8。如果软件在文件中找到前3个字节是“幻数”
EF
、
BB
和
BF
,它可以推断该文件以UTF-8存储。这三个字节被丢弃,其余部分从UTF-8解码。
特别地,Microsoft Windows在其大多数软件中使用此技术。显然,在Excel的情况下也适用,因此总结如下:
- 您使用
df.to_csv("file.csv", encoding="utf-8-sig")
编写csv文件
- Excel读取文件并在开头找到
EF
、BB
和BF
。因此,它会丢弃这些字节,并假定文件的其余部分为utf-8。
- 稍后,序列
c2
、b1
出现在文件中,它将正确解码为UTF-8,生成±
这具有在任何Windows计算机上工作的优点,无论它使用的代码页是什么(cp1252用于西欧,其他国家可能使用其他代码页,但Unicode和UTF-8是通用的)。
潜在问题是,如果您尝试在非Windows机器上读取此csv文件,则可能会出现问题。软件可能无法识别前三个“魔术字节”EF、BB、BF的含义。因此,您可能会在文件开头遇到“虚假”的字符,这可能会导致问题。如果读取文件的软件假定UTF-8编码,则这三个字节将被解码为Unicode字符FFFE,但它们不会被丢弃。该字符是不可见的,宽度为零,因此任何编辑器都无法“看到”它,但它仍然存在。如果读取文件的软件假定其他任何编码,例如“latin1”,则这三个字节将被错误地解码为,并且它们将在文件开头可见。
如果您使用Python读回此文件,则必须再次指定utf-8-sig编码,以使Python丢弃这三个初始字节。
ñ
而不是±
。你能编辑你的帖子并包含你用来写入CSV的代码吗? - Michael Kolberñ
。 - mzjn