2013-06-25 12 views
10

Ho un database (mysql) in cui voglio memorizzare i dati in pickled.Come decodificare gli unicodi e salvarli nei database utf-8

I dati possono essere ad esempio un dizionario, che può contenere unicode, ad es.

data = {1 : u'é'} 

e il database (mysql) è in utf-8.

Quando ho Pickle,

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

il pickled_data risultante è una stringa.

Quando si tenta di archiviare questo in un database (ad esempio in un campo di testo), questo può causare problemi. In particolare, sto ottenendo ad un certo punto uno

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

quando si tenta di salvare il pickled_data nel database. Questo ha senso perché pickled_data può avere caratteri non-utf-8. La mia domanda è: come immagazzino pickled_data su un database utf-8?

Vedo due possibili candidati:

  1. Encode il risultato della pickle.dump in UTF-8 e conservarla. Quando voglio pickle.load, devo decodificarlo.

  2. Memorizza la stringa decapata in formato binario (come?), Che obbliga tutti i caratteri ad essere all'interno di ascii.

Il mio problema è che non vedo quali sono le conseguenze della scelta di una di queste opzioni a lungo termine. Dato che il cambiamento richiede già qualche sforzo, sono spinto a chiedere un parere su questo tema, chiedendo eventuali candidati migliori.

(PS Questo è utile ad esempio in Django)

+0

Opzione 3: Archivia i dati Unicode come stringhe codificate UTF-8. –

+0

Opzione 4: utilizzare invece un tipo di colonna binario. –

+2

I dati di pickle sono dati * binari *. Non puoi codificarlo in UTF-8 (una codifica di testo). –

risposta

13

dati Pickle è opaco, dati binari, anche quando si utilizza la versione di protocollo 0:

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

Quando si tenta di memorizzare che in un TextField , Django proverà a decodificare i dati in UTF8 per memorizzarli; questo è ciò che fallisce perché non si tratta di dati codificati UTF-8; si tratta di dati binari, invece:

>>> 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 

La soluzione è quella di non cerca di memorizzare questo in un TextField. Utilizzare invece :

Un campo per memorizzare dati binari non elaborati. Supporta solo l'assegnazione bytes. Essere consapevoli del fatto che questo campo ha funzionalità limitate. Ad esempio, non è possibile filtrare un set di query su un valore di BinaryField.

Si hanno un valore bytes (Python 2 stringhe sono stringhe di byte, rinominati per bytes in Python 3).

Se si desidera memorizzare i dati in un campo di testo, decodificarlo esplicitamente come latin1; le mappe codec Latina 1 byte one-to-one per codepoints Unicode:

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

e assicurarsi che si codificare di nuovo prima di deserializzazione ancora:

>>> 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'} 

si noti che se si lascia questo valore vai al browser e torna indietro in un campo di testo, è probabile che il browser abbia sostituito i caratteri in quei dati. Internet Explorer sostituirà i caratteri \n con \r\n, ad esempio, perché presuppone che si tratti di testo.

Non si dovrebbe mai consentire di accettare i dati di pickle da una connessione di rete in ogni caso, perché that is a security hole waiting for exploitation.

+0

Aspetterò il campo binario di django in stable. Nel frattempo userò TextField. Grazie per la risposta. –

+0

Mi scuso; Non avevo apprezzato il fatto che 'BinaryField' sia stato appena aggiunto a una versione di sviluppo. Sono sorpreso che Django non avesse un campo di dati binari prima. –

+1

@ J.C.Leitão: E nel caso in cui questi sottaceti siano dati accettati dalla rete, leggere http://www.zopatista.com/plone/2007/11/09/one-cookie-please/; i dati di pickle dovrebbero ** non essere mai accettati da fonti non attendibili. –