Как разбить юникоды и сохранить их в базах данных utf-8

У меня есть база данных (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?

Я вижу двух возможных кандидатов:

  • Кодировать результат pickle.dump на utf-8 и хранить его. Когда я хочу pickle.load, я должен его декодировать.

  • Хранить маринованную строку в двоичном формате (как?), что заставляет все символы находиться в пределах ascii.

Моя проблема в том, что я не вижу, каковы последствия выбора одного из этих вариантов в долгосрочной перспективе. Поскольку изменение уже требует определенных усилий, я вынужден спросить мнение по этому вопросу, попросив возможных лучших кандидатов.

(P.S.Это, например, полезно в Django)

Ответ 1

Данные сокета являются непрозрачными двоичными данными, даже если вы используете версию протокола 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 - это байтовые строки, переименованные в bytes в Python 3).

Если вы настаиваете на сохранении данных в текстовом поле, явным образом расшифруйте его как latin1; латинский кодек 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, поскольку предполагает, что он имеет дело с текстом.

Не то, чтобы вы когда-либо позволяли принимать данные о сортировке из сетевого соединения в любом случае, потому что