【问题标题】:How to pickle unicodes and save them in utf-8 databases如何腌制 unicode 并将它们保存在 utf-8 数据库中
【发布时间】:2013-06-22 14:24:24
【问题描述】:

我有一个数据库 (mysql),我想在其中存储腌制数据。

数据可以是例如字典,其中可能包含 unicode,例如

data = {1 : u'é'}

数据库(mysql)是utf-8格式。

当我腌制时,

import pickle
pickled_data = pickle.dumps(data)
print type(pickled_data) # returns <type 'str'>

得到的 pickled_data 是一个字符串。

当我尝试将其存储在数据库中(例如,在文本字段中)时,这可能会导致问题。特别是,我在某个时候得到了一个

UnicodeDecodeError "'utf8' codec can't decode byte 0xe9 in position X"

尝试将 pickled_data 保存在数据库中时。这是有道理的,因为 pickled_data 可以有非 utf-8 字符。我的问题是如何将 pickled_data 存储在 utf-8 数据库中?

我看到两个可能的候选人:

  1. 将 pickle.dump 的结果编码为 utf-8 并存储。当我想pickle.load时,我必须对其进行解码。

  2. 以二进制格式存储腌制字符串(如何?),这会强制所有字符都在 ascii 内。

我的问题是,从长远来看,我没有看到选择其中一个选项的后果。由于更改已经需要一些努力,因此我想就这个问题征求意见,寻求最终更好的候选人。

(P.S. 例如,这在Django 中很有用)

【问题讨论】:

  • 选项 3:将 unicode 数据存储为 UTF-8 编码字符串。
  • 选项 4:改用二进制列类型。
  • 泡菜数据是二进制数据。您不能将其编码为 UTF-8(文本编码)。
  • @MartijnPieters,我编辑并改进了这个问题。感谢您的意见。
  • 我并不是唯一一个被 Python 2 文档中有关 pickle 协议 0 的措辞所吸引的人。我看到他们改进了 Python 3 文档中的措辞,以明确所有协议都是二进制的。

标签: python django unicode utf-8 pickle


【解决方案1】:

Pickle 数据是不透明的,二进制数据,即使你使用协议版本 0:

>>> pickle.dumps(data, 0)
'(dp0\nI1\nV\xe9\np1\ns.'

当您尝试将其存储在 TextField 中时,Django 将尝试将该数据解码为 UTF8 以存储它;这就是失败的原因,因为这不是 UTF-8 编码的数据;它是二进制数据:

>>> pickled_data.decode('utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mj/Development/venvs/stackoverflow-2.7/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xe9 in position 9: invalid continuation byte

解决方案是不尝试将其存储在TextField 中。请改用BinaryField:

存储原始二进制数据的字段。它只支持bytes 赋值。请注意,此字段的功能有限。例如,无法根据 BinaryField 值过滤查询集。

您有一个 bytes 值(Python 2 字符串是字节字符串,在 Python 3 中重命名为 bytes)。

如果您坚持将数据存储在文本字段中,请将其显式解码为latin1; Latin 1 编解码器将字节一对一地映射到 Unicode 代码点:

>>> pickled_data.decode('latin1')
u'(dp0\nI1\nV\xe9\np1\ns.'

并确保在再次解封之前再次编码:

>>> encoded = pickled_data.decode('latin1')
>>> pickle.loads(encoded)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mj/Development/Libraries/buildout.python/parts/opt/lib/python2.7/pickle.py", line 1381, in loads
    file = StringIO(str)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 9: ordinal not in range(128)
>>> pickle.loads(encoded.encode('latin1'))
{1: u'\xe9'}

请注意,如果您让该值进入浏览器并在文本字段中再次返回,浏览器很可能已经替换了该数据中的字符。例如,Internet Explorer 会将 \n 字符替换为 \r\n,因为它假定它正在处理文本。

在任何情况下,您都不应该允许从网络连接接受泡菜数据,因为that is a security hole waiting for exploitation。

【讨论】:

  • 我会等待 django 的 binaryField 稳定。同时我将使用TextField。感谢您的回答。
  • 我很抱歉;我没有意识到 BinaryField 刚刚被添加到开发版本中。我很惊讶 Django 之前没有二进制数据字段。
  • @J.C.Leitão:如果这些泡菜是您从网络接受的数据,请阅读zopatista.com/plone/2007/11/09/one-cookie-please;泡菜数据应该绝不接受来自不受信任的来源。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-29
  • 1970-01-01
  • 2012-12-19
  • 1970-01-01
  • 1970-01-01
  • 2012-10-12
相关资源
最近更新 更多