如何在Python中写入原始二进制数据?
我有一个Python程序,它可以把数据存储到文件里。这个数据是原始的二进制数据,内部是用 str
类型存储的。我通过utf-8编码把它写出去。但是,我在 cp1252.py
文件里遇到了一个错误:UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 25: character maps to <undefined>
。
看起来Python在试图用默认的编码方式来解释这些数据。但实际上,它并没有一个默认的编码方式。这就是我为什么使用 str
而不是 unicode
的原因。
我想问的问题是:
- 在Python中,我该如何在内存里表示原始的二进制数据?
- 当我通过编码方式把原始二进制数据写出去时,我该如何进行编码和解码?
3 个回答
关于你的第一个问题:在Python中,普通字符串(也就是不是unicode字符串的那些)其实是二进制数据。如果你想把unicode字符串和二进制数据一起写入,就需要先把unicode字符串转换成二进制数据,然后再把它们放在一起:
# encode the unicode string as a string
bytes = unicodeString.encode('utf-8')
# add it to the other string
raw_data += bytes
# write it all to a file
yourFile.write(raw_data)
对于你的第二个问题:你需要用write()
来写入原始数据;然后,当你读取这些数据时,可以这样做:
import codecs
yourFile = codecs.open( "yourFileName", "r", "utf-8" )
# and now just use yourFile.read() to read it
一般来说,你不应该把编码器(codecs)和字符串(str
)一起用,除非是为了把它们转换成Unicode格式的字符串(unicode
)。如果你觉得需要在Unicode中获取“原始”数据,或许可以考虑使用latin-1
这种编码。
注意:这段内容是为Python 2.x写的。不确定是否适用于3.x。
你在内存中使用str
来处理原始二进制数据是正确的。
[如果你使用的是Python 2.6及以上版本,使用bytes
会更好,因为在2.6及以上版本中,bytes
只是str
的别名,但它能更好地表达你的意图,并且将来如果你把代码移植到Python 3时会更有帮助。]
正如其他人所提到的,通过编解码器写入二进制数据是有点奇怪的。一个写入编解码器接收的是unicode,然后输出的是字节到文件中。你现在的做法是反过来的,所以我们对你的意图感到困惑……
[而且你对错误的诊断看起来是正确的:因为编解码器期望的是unicode,所以Python正在用系统的默认编码把你的str
解码成unicode,这样就出错了。]
你想在输出文件中看到什么?
如果文件应该包含原始的二进制数据:
那么你就不能通过编解码器来处理它;你必须直接写入文件。编解码器会对所有内容进行编码,只能输出有效的unicode编码(在你的情况下是有效的UTF-8)。你无法给它输入任何内容来让它输出任意的字节序列!
- 如果你需要混合UTF-8和原始二进制数据,你应该直接打开文件,并交替写入
some_data
和some_text.encode('utf8')
……
不过要注意,混合UTF-8和原始任意数据是非常糟糕的设计,因为这样的文件处理起来非常麻烦!理解unicode的工具会在遇到二进制数据时出错,让你根本无法方便地查看(更不用说修改)这个文件。
- 如果你需要混合UTF-8和原始二进制数据,你应该直接打开文件,并交替写入
如果你想要一个友好的表示方式来显示任意字节的unicode:
可以把
data.encode('base64')
传给编解码器。Base64只生成干净的ascii(字母、数字和少量标点),所以它可以清晰地嵌入到任何地方,给人一种明显是二进制数据的感觉,而且它的体积也比较小(大约多33%的开销)。附注:你可能会觉得
data.encode('base64')
有点奇怪。.encode()
本来是用来处理unicode的,但我却给它一个字符串?Python有几种伪编解码器可以把str转为str,比如'base64'和'zlib'。.encode()
总是返回一个str,但你却把它放进一个期望unicode的编解码器里?在这种情况下,它只会包含干净的ascii,所以没关系。如果这样让你感觉更好,你可以明确写成data.encode('base64').encode('utf8')
。
如果你需要任意字节和unicode之间的1:1映射:
可以把
data.decode('latin1')
传给编解码器。latin1
把字节0-255映射到unicode字符0-255,这样的映射方式还算优雅。当然,编解码器会对你的字符进行编码——128-255的字符在UTF-8中会被编码为2或3个字节(令人惊讶的是,平均开销是50%,比base64还要多!)。这就大大降低了1:1映射的“优雅性”。
还要注意,unicode字符0-255中包含一些讨厌的不可见/控制字符(换行、分页、软连字符等),这会让你的二进制数据在文本编辑器中查看时非常麻烦。
考虑到这些缺点,我不推荐使用latin1,除非你确切知道为什么要使用它。
我只是提到它,因为这是另一个让我想到的“自然”编码。