Source

diveintopython3-it / serializzare-oggetti-python.html

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
<!DOCTYPE html>
<meta charset=utf-8>
<title>Serializzare oggetti Python - Immersione in Python 3</title>
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 13}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
<meta name=viewport content='initial-scale=1.0'>
<form action=http://www.google.com/cse><div><input type=hidden name=cx value=014021643941856155761:l5eihuescdw><input type=hidden name=ie value=UTF-8>&nbsp;<input type=search name=q size=25 placeholder="powered by Google&trade;">&nbsp;<input type=submit name=root value=Search></div></form>
<p>Voi siete qui: <a href=index.html>Inizio</a> <span class=u>&#8227;</span> <a href=indice.html#serializzare-oggetti-python>Immersione in Python 3</a> <span class=u>&#8227;</span>
<p id=level>Livello di difficoltà: <span class=u title=avanzato>&#x2666;&#x2666;&#x2666;&#x2666;&#x2662;</span>
<h1>Serializzare oggetti Python</h1>
<blockquote class=q>
<p><span class=u>&#x275D;</span> Da quando viviamo in questo appartamento, ogni sabato mi sono alzato alle 6:15, mi sono preparato una tazza di cereali con 1/16 di litro di latte parzialmente scremato, mi sono seduto su <strong>questo</strong> lato di <strong>questo</strong> divano, ho acceso la TV su BBC America e ho guardato Doctor Who. <span class=u>&#x275E;</span><br>&mdash; Sheldon, <a href='http://en.wikiquote.org/wiki/The_Big_Bang_Theory#The_Dumpling_Paradox_.5B1.07.5D'>The Big Bang Theory</a>
</blockquote>
<p id=toc>&nbsp;
<h2 id=divingin>Immersione!</h2>
<p class=f>Quando lo analizzate in superficie, il concetto di <dfn>serializzazione</dfn> è semplice. Avete in memoria una struttura dati che volete salvare, riutilizzare, o inviare a qualcun altro. Come fareste? Be&#8217;, questo dipende da come volete salvarla, dal modo in cui pensate di riutilizzarla e dal destinatario a cui desiderate inviarla. Molti videogiochi vi permettono di salvare i vostri progressi quando uscite dal gioco e di ricominciare da dove eravate rimasti quando rientrate nel gioco. (In realtà, anche molte applicazioni che non sono giochi lo fanno.) In questo caso, una struttura dati che cattura &#8220;i vostri progressi finora&#8221; deve essere memorizzata su disco quando uscite, poi caricata dal disco quando rientrate. I dati sono pensati solo per essere usati dallo stesso programma che li ha creati, mai per venire inviati in rete né per essere letti da altri programmi che non siano quello che li ha creati. Quindi, l&#8217;interoperabilità si limita a garantire che versioni più recenti del programma possano leggere i dati salvati dalle versioni precedenti.

<p>Per casi come questi, il modulo <code>pickle</code> è l&#8217;ideale: fa parte della libreria standard di Python, quindi è sempre disponibile; è veloce, perché la maggior parte del modulo è scritta in C, come lo stesso interprete Python; e può memorizzare strutture dati Python arbitrariamente complesse.

<p>Cosa può memorizzare il modulo <code>pickle</code>?

<ul>
<li>Tutti i <a href=tipi-di-dato-nativi.html>tipi di dato nativi</a> supportati da Python: booleani, interi, numeri in virgola mobile, numeri complessi, stringhe, oggetti <code>bytes</code>, array di byte e <code>None</code>.
<li>Liste, tuple, dizionari e insiemi contenenti qualsiasi combinazione di tipi di dato nativi.
<li>Liste, tuple, dizionari e insiemi contenenti qualsiasi combinazione di liste, tuple, dizionari e insiemi contenenti qualsiasi combinazione di tipi di dato nativi (e così via, fino al <a title='sys.getrecursionlimit()' href=http://docs.python.org/3.1/library/sys.html#sys.getrecursionlimit>massimo livello di annidamento supportato da Python</a>).
<li>Funzioni, classi e istanze di classi (con alcune avvertenze).
</ul>

<p>Se questo non vi basta, potete anche estendere il modulo <code>pickle</code>. Se siete interessati alla estendibilità, controllate le <a href=#furtherreading>letture di approfondimento</a> al termine del capitolo.

<h3 id=administrivia>Una nota rapida sugli esempi di questo capitolo</h3>

<p>Questo capitolo narra il proprio racconto usando due Shell Python. Tutti gli esempi in questo capitolo fanno parte dell&#8217;arco di una singola storia. Vi verrà chiesto di spostarvi avanti e indietro tra le due Shell Python man mano che vi mostro le funzioni dei moduli <code>pickle</code> e <code>json</code>.

<p>Per evitare di confondervi, aprite una Shell Python e definite la seguente variabile:

<pre class='nd screen'>
<samp class=p>>>> </samp><kbd class=pp>shell = 1</kbd></pre>

<p>Tenete aperta quella finestra. Ora aprite un&#8217;altra Shell Python e definite la seguente variabile:

<pre class='nd screen'>
<samp class=p>>>> </samp><kbd class=pp>shell = 2</kbd></pre>

<p>In tutto questo capitolo, userò la variabile <code>shell</code> per indicare la Shell Python che viene usata in ogni esempio.

<p class=a>&#x2042;

<h2 id=dump>Salvare dati in un file pickle</h2>

<p>Il modulo <code>pickle</code> lavora con le strutture dati. Costruiamone una.

<pre class=screen>
<a><samp class=p>>>> </samp><kbd class=pp>shell</kbd>                                                                                              <span class=u>&#x2460;</span></a>
<samp class=pp>1</samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry = {}</kbd>                                                                                         <span class=u>&#x2461;</span></a>
<samp class=p>>>> </samp><kbd class=pp>entry['title'] = 'Immersione nella storia, edizione 2009'</kbd>
<samp class=p>>>> </samp><kbd class=pp>entry['article_link'] = 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'</kbd>
<samp class=p>>>> </samp><kbd class=pp>entry['comments_link'] = None</kbd>
<samp class=p>>>> </samp><kbd class=pp>entry['internal_id'] = b'\xDE\xD5\xB4\xF8'</kbd>
<samp class=p>>>> </samp><kbd class=pp>entry['tags'] = ('diveintopython', 'docbook', 'html')</kbd>
<samp class=p>>>> </samp><kbd class=pp>entry['published'] = True</kbd>
<samp class=p>>>> </samp><kbd class=pp>import time</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>entry['published_date'] = time.strptime('Fri Mar 27 22:20:42 2009')</kbd>                                <span class=u>&#x2462;</span></a>
<samp class=p>>>> </samp><kbd class=pp>entry['published_date']</kbd>
<samp class=pp>time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1)</samp></pre>
<ol>
<li>Seguitemi nella Shell Python n°1.
<li>L&#8217;idea qui è quella di costruire un dizionario Python che possa rappresentare qualcosa di utile, come una <a href=xml.html#xml-structure>voce in un feed Atom</a>. Ma voglio anche assicurarmi che contenga diversi tipi di dato, per mettere in risalto il modulo <code>pickle</code>. Questi valori non hanno alcun significato particolare.
<li>Il modulo <code>time</code> contiene una struttura dati (<code>time_struct</code>) per rappresentare un punto nel tempo (accurato al millisecondo) e alcune funzioni per manipolare queste strutture. La funzione <code>strptime()</code> prende una stringa formattata e la converte in un oggetto <code>time_struct</code>. Il formato di questa stringa è quello predefinito, ma potete controllarlo con i codici di formato. Leggete la documentazione sul <a href=http://docs.python.org/3.1/library/time.html>modulo <code>time</code></a> per maggiori dettagli.
</ol>

<p>Questo sembra proprio un bel dizionario Python. Salviamolo in un file.

<pre class=screen>
<a><samp class=p>>>> </samp><kbd class=pp>shell</kbd>                                    <span class=u>&#x2460;</span></a>
<samp class=pp>1</samp>
<samp class=p>>>> </samp><kbd class=pp>import pickle</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>with open('entry.pickle', 'wb') as f:</kbd>    <span class=u>&#x2461;</span></a>
<a><samp class=p>... </samp><kbd class=pp>    pickle.dump(entry, f)</kbd>                <span class=u>&#x2462;</span></a>
<samp class=p>... </samp></pre>
<ol>
<li>Ci troviamo ancora nella Shell Python n°1.
<li>Usiamo la funzione <code>open()</code> per aprire il file, impostando la modalità a <code>'wb'</code> in modo da aprirlo in scrittura <a href=file.html#binary>in modalità binaria</a>. Circondiamo la funzione con una <a href=file.html#with>istruzione <code>with</code></a> per avere la garanzia che il file venga chiuso automaticamente quando abbiamo finito di lavorare.
<li>La funzione <code>dump()</code> del modulo <code>pickle</code> prende una struttura dati Python serializzabile, la serializza in un formato binario specifico per Python usando la versione più recente del protocollo pickle e ne salva la forma serializzata in un file aperto.
</ol>

<p>Quest&#8217;ultima frase è molto importante.

<ul>
<li>Il modulo <code>pickle</code> prende una struttura dati Python e la salva in un file.
<li>Per fare questo, <i>serializza</i> la struttura dati usando un formato di dati chiamato &#8220;protocollo pickle&#8221;.
<li>Il protocollo pickle è specifico per Python; non c&#8217;è alcuna garanzia di compatibilità verso altri linguaggi. Probabilmente non potreste prendere il file <code>entry.pickle</code> che avete appena creato e farci qualcosa di utile in Perl, <abbr>PHP</abbr>, Java, o qualsiasi altro linguaggio.
<li>Non tutte le strutture dati Python possono essere serializzate dal modulo <code>pickle</code>. Il protocollo pickle è cambiato diverse volte man mano che nuovi tipi di dato sono stati aggiunti al linguaggio Python, ma ci sono ancora alcune restrizioni.
<li>Come risultato di questi cambiamenti, non c&#8217;è alcuna garanzia di compatibilità tra diverse versioni di Python. Le versioni più recenti supportano i formati di serializzazione più vecchi, ma le vecchie versioni di Python non supportano i nuovi formati (dato che non supportano i nuovi tipi di dato).
<li>A meno che non specifichiate diversamente, le funzioni del modulo <code>pickle</code> useranno la versione più recente del protocollo pickle. Questo vi garantisce la massima flessibilità nei tipi di dato che potete serializzare, ma significa anche che il file risultante non potrà essere letto da vecchie versioni di Python che non supportano la versione più recente del protocollo pickle.
<li>L&#8217;ultima versione del protocollo pickle è un formato binario. Assicuratevi di aprire i vostri file pickle <a href=file.html#binary>in modalità binaria</a>, o i dati verranno rovinati durante la scrittura.
</ul>

<p class=a>&#x2042;

<h2 id=load>Caricare dati da un file pickle</h2>

<p>Ora spostatevi nella seconda Shell Python&nbsp;&mdash;&nbsp;cioè  quella in cui <em>non</em> avete creato il dizionario <code>entry</code>.

<pre class=screen>
<a><samp class=p>>>> </samp><kbd class=pp>shell</kbd>                                    <span class=u>&#x2460;</span></a>
<samp class=pp>2</samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry</kbd>                                    <span class=u>&#x2461;</span></a>
<samp class=traceback>Traceback (most recent call last):
  File "&lt;stdin>", line 1, in &lt;module>
NameError: name 'entry' is not defined</samp>
<samp class=p>>>> </samp><kbd class=pp>import pickle</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>with open('entry.pickle', 'rb') as f:</kbd>    <span class=u>&#x2462;</span></a>
<a><samp class=p>... </samp><kbd class=pp>    entry = pickle.load(f)</kbd>               <span class=u>&#x2463;</span></a>
<samp class=p>... </samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry</kbd>                                    <span class=u>&#x2464;</span></a>
<samp class=pp>{'comments_link': None,
 'internal_id': b'\xDE\xD5\xB4\xF8',
 'title': 'Immersione nella storia, edizione 2009',
 'tags': ('diveintopython', 'docbook', 'html'),
 'article_link':
 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',
 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),
 'published': True}</samp></pre>
<ol>
<li>Questa è la Shell Python n°2.
<li>Non c&#8217;è alcuna variabile <var>entry</var> definita qui. Avete definito una variabile <var>entry</var> nella Shell Python n°1, ma questo è un ambiente con un proprio stato completamente differente.
<li>Aprite il file <code>entry.pickle</code> che avete creato nella Shell Python n°1. Il modulo <code>pickle</code> usa un formato di dati binario, quindi dovreste sempre aprire i file pickle in modalità binaria.
<li>La funzione <code>pickle.load()</code> prende un <a href=file.html#file-objects>oggetto stream</a>, legge i dati serializzati dal flusso, crea un nuovo oggetto Python, ricrea i dati serializzati nel nuovo oggetto Python e infine restituisce questo nuovo oggetto.
<li>Ora la variabile <var>entry</var> è un dizionario contenente chiavi e valori che sembrano familiari.
</ol>

<p>Il ciclo <code>pickle.dump()</code> / <code>pickle.load()</code> dà come risultato una nuova struttura dati che è uguale alla struttura dati originale.

<pre class=screen>
<a><samp class=p>>>> </samp><kbd class=pp>shell</kbd>                                    <span class=u>&#x2460;</span></a>
<samp class=pp>1</samp>
<a><samp class=p>>>> </samp><kbd class=pp>with open('entry.pickle', 'rb') as f:</kbd>    <span class=u>&#x2461;</span></a>
<a><samp class=p>... </samp><kbd class=pp>    entry2 = pickle.load(f)</kbd>              <span class=u>&#x2462;</span></a>
<samp class=p>... </samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry2 == entry</kbd>                          <span class=u>&#x2463;</span></a>
<samp class=pp>True</samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry2 is entry</kbd>                          <span class=u>&#x2464;</span></a>
<samp class=pp>False</samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry2['tags']</kbd>                           <span class=u>&#x2465;</span></a>
<samp class=pp>('diveintopython', 'docbook', 'html')</samp>
<samp class=p>>>> </samp><kbd class=pp>entry2['internal_id']</kbd>
<samp class=pp>b'\xDE\xD5\xB4\xF8'</samp></pre>
<ol>
<li>Tornate indietro alla Shell Python n°1.
<li>Aprite il file <code>entry.pickle</code>.
<li>Caricate i dati serializzati in una nuova variabile chiamata <var>entry2</var>.
<li>Python conferma che i due dizionari <var>entry</var> ed <var>entry2</var> sono uguali. In questa shell avete costruito <var>entry</var> da zero, cominciando con un dizionario vuoto e assegnando manualmente i valori a chiavi specifiche. Avete serializzato questo dizionario memorizzandolo nel file <code>entry.pickle</code>. Ora avete letto i dati serializzati da quel file e avete creato una riproduzione perfetta della struttura dati originale.
<li>L&#8217;uguaglianza non è la stessa cosa dell&#8217;identità. Ho detto che avete creato una <em>riproduzione perfetta</em> della struttura dati originale, ed è vero, ma è ancora una copia.
<li>Per ragioni che diventeranno chiare più avanti in questo capitolo, voglio sottolineare che il valore della chiave <code>'tags'</code> è una tupla e che il valore della chiave <code>'internal_id'</code> è un oggetto <code>bytes</code>.
</ol>

<p class=a>&#x2042;

<h2 id=dumps>Serializzare senza un file</h2>

<p>Gli esempi nelle sezioni precedenti hanno mostrato come serializzare un oggetto Python direttamente in un file su disco. E se voi non voleste o non aveste bisogno di un file? Potete anche serializzare in un oggetto <code>bytes</code> in memoria.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>1</samp>
<a><samp class=p>>>> </samp><kbd class=pp>b = pickle.dumps(entry)</kbd>     <span class=u>&#x2460;</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>type(b)</kbd>                     <span class=u>&#x2461;</span></a>
<samp>&lt;class 'bytes'></samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry3 = pickle.loads(b)</kbd>    <span class=u>&#x2462;</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>entry3 == entry</kbd>             <span class=u>&#x2463;</span></a>
<samp class=pp>True</samp></pre>
<ol>
<li>La funzione <code>pickle.dumps()</code> (notate la <code>'s'</code> alla fine del nome della funzione) effettua la stessa serializzazione della funzione <code>pickle.dump()</code>, ma invece di prendere un oggetto stream e scrivere i dati serializzati in un file su disco restituisce semplicemente i dati serializzati.
<li>Dato che il protocollo pickle usa un formato di dati binario, la funzione <code>pickle.dumps()</code> restituisce un oggetto <code>bytes</code>.
<li>La funzione <code>pickle.loads()</code> (anche in questo caso, notate la <code>'s'</code> alla fine del nome della funzione) effettua la stessa deserializzazione della funzione <code>pickle.load()</code>. Invece di prendere un oggetto stream e leggere i dati serializzati da un file, prende un oggetto <code>bytes</code> contenente dati serializzati, come quello restituito dalla funzione <code>pickle.dumps()</code>.
<li>Il risultato finale è lo stesso: una riproduzione perfetta del dizionario originale.
</ol>

<p class=a>&#x2042;

<h2 id=protocol-versions>I byte e le stringhe sollevano ancora la loro ripugnante testa</h2>

<p>Il protocollo pickle esiste da molti anni ed è maturato man mano che lo stesso Python è maturato. Ora ci sono <a href=http://docs.python.org/3.1/library/pickle.html#data-stream-format>quattro versioni differenti</a> del protocollo pickle.

<ul>
<li>Python 1.x aveva due protocolli pickle: un formato basato su testo (&#8220;versione 0&#8221;) e un formato binario (&#8220;versione 1&#8221;).
<li>Python 2.3 ha introdotto un nuovo protocollo pickle (&#8220;versione 2&#8221;) per gestire nuove funzioni negli oggetti classe di Python. Questo è un formato binario.
<li>Python 3.0 ha introdotto un altro protocollo pickle (&#8220;versione 3&#8221;) con supporto esplicito per gli oggetti <code>bytes</code> e gli array di byte. Questo è un formato binario.
</ul>

<p>Oh, guardate, <a href=stringhe.html#byte-arrays>la differenza tra byte e stringhe</a> solleva la sua ripugnante testa ancora una volta. (Se questo vi sorprende, non siete stati molto attenti.) In pratica, questo significa che mentre Python 3 è in grado di leggere dati serializzati con la versione 2 del protocollo, Python 2 non è in grado di leggere dati serializzati con la versione 3 del protocollo.

<p class=a>&#x2042;

<h2 id=debugging>Effettuare il debug dei file pickle</h2>

<p>Che aspetto ha il protocollo pickle? Abbandoniamo la Shell Python per un momento e diamo un&#8217;occhiata a quel file <code>entry.pickle</code> che abbiamo creato.

<pre class=screen>
<samp class=p>you@localhost:~/diveintopython3/esempi$ </samp><kbd>ls -l entry.pickle</kbd>
<samp>-rw-r--r-- 1 you  you  365 Aug  3 13:34 entry.pickle</samp>
<samp class=p>you@localhost:~/diveintopython3/esempi$ </samp><kbd>cat entry.pickle</kbd>
<samp>comments_linkqNXtagsqXdiveintopythonqXdocbookqXhtmlq?qX publishedq?
Xarticle_linkXJhttp://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition
q   Xpublished_dateq
ctime
struct_time
?qRqXtitleqXImmersione nella storia, edizione 2009qu.</samp></pre>

<p>Questo non ci aiuta molto. Potete vedere le stringhe, ma gli altri tipi di dato sono composti da caratteri non stampabili (o quantomeno non leggibili). I campi non sono ovviamente delimitati da tabulazioni o spazi. Questo non è un formato di cui vorreste effettuare il debug da soli.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>1</samp>
<samp class=p>>>> </samp><kbd class=pp>import pickletools</kbd>
<samp class=p>>>> </samp><kbd class=pp>with open('entry.pickle', 'rb') as f:</kbd>
<samp class=p>... </samp><kbd class=pp>    pickletools.dis(f)</kbd>
<samp>    0: \x80 PROTO      3
    2: }    EMPTY_DICT
    3: q    BINPUT     0
    5: (    MARK
    6: X        BINUNICODE 'published_date'
   25: q        BINPUT     1
   27: c        GLOBAL     'time struct_time'
   45: q        BINPUT     2
   47: (        MARK
   48: M            BININT2    2009
   51: K            BININT1    3
   53: K            BININT1    27
   55: K            BININT1    22
   57: K            BININT1    20
   59: K            BININT1    42
   61: K            BININT1    4
   63: K            BININT1    86
   65: J            BININT     -1
   70: t            TUPLE      (MARK at 47)
   71: q        BINPUT     3
   73: }        EMPTY_DICT
   74: q        BINPUT     4
   76: \x86     TUPLE2
   77: q        BINPUT     5
   79: R        REDUCE
   80: q        BINPUT     6
   82: X        BINUNICODE 'comments_link'
  100: q        BINPUT     7
  102: N        NONE
  103: X        BINUNICODE 'internal_id'
  119: q        BINPUT     8
  121: C        SHORT_BINBYTES 'ÞÕ´ø'
  127: q        BINPUT     9
  129: X        BINUNICODE 'tags'
  138: q        BINPUT     10
  140: X        BINUNICODE 'diveintopython'
  159: q        BINPUT     11
  161: X        BINUNICODE 'docbook'
  173: q        BINPUT     12
  175: X        BINUNICODE 'html'
  184: q        BINPUT     13
  186: \x87     TUPLE3
  187: q        BINPUT     14
  189: X        BINUNICODE 'title'
  199: q        BINPUT     15
  201: X        BINUNICODE 'Immersione nella storia, edizione 2009'
  244: q        BINPUT     16
  246: X        BINUNICODE 'article_link'
  263: q        BINPUT     17
  265: X        BINUNICODE 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'
  344: q        BINPUT     18
  346: X        BINUNICODE 'published'
  360: q        BINPUT     19
  362: \x88     NEWTRUE
  363: u        SETITEMS   (MARK at 5)
  364: .    STOP
<mark>highest protocol among opcodes = 3</mark></samp></pre>

<p>L&#8217;informazione più interessante in questo disassemblato si trova sull&#8217;ultima riga, perché include la versione del protocollo pickle con la quale questo file è stato salvato. Non c&#8217;è alcun contrassegno di versione esplicito nel protocollo pickle. Per determinare la versione del protocollo che è stata usata per memorizzare un file pickle, dovete esaminare i contrassegni (chiamati &#8220;opcode&#8221; in inglese) all&#8217;interno dei dati serializzati e usare la conoscenza cablata di quali opcode sono stati introdotti con ogni versione del protocollo pickle. La funzione <code>pickle.dis()</code> fa proprio questo e stampa il risultato nell&#8217;ultima riga del disassemblato. Ecco una funzione che restituisce solo il numero di versione, senza stampare nulla:

<p class=d>[<a href=esempi/pickleversion.py>scarica <code>pickleversion.py</code></a>]
<pre class=pp><code>import pickletools

def protocol_version(file_object):
    maxproto = -1
    for opcode, arg, pos in pickletools.genops(file_object):
        maxproto = max(maxproto, opcode.proto)
    return maxproto</code></pre>

<p>Ed eccola qui in azione:</p>

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>import pickleversion</kbd>
<samp class=p>>>> </samp><kbd class=pp>with open('entry.pickle', 'rb') as f:</kbd>
<samp class=p>... </samp><kbd class=pp>    v = pickleversion.protocol_version(f)</kbd>
<samp class=p>>>> </samp><kbd class=pp>v</kbd>
<samp class=pp>3</samp></pre>

<p class=a>&#x2042;

<h2 id=json>Serializzare oggetti Python per leggerli con altri linguaggi</h2>

<p>Il formato di dati usato dal modulo <code>pickle</code> è specifico per Python. Non cerca in alcun modo di essere compatibile con altri linguaggi di programmazione. Se la compatibilità verso altri linguaggi è uno dei vostri requisiti, dovete rivolgervi a formati di serializzazione differenti. Uno di questi formati è <a href=http://json.org/><abbr>JSON</abbr></a>. &#8220;<abbr>JSON</abbr>&#8221; sta per &#8220;JavaScript Object Notation&#8221; (letteralmente, notazione degli oggetti JavaScript) ma non fatevi ingannare dal nome&nbsp;&mdash;&nbsp;<abbr>JSON</abbr> è stato esplicitamente progettato per poter essere usato con molteplici linguaggi di programmazione.

<p>Python 3 include un modulo <code>json</code> nella libreria standard. Come il modulo <code>pickle</code>, anche il modulo <code>json</code> è dotato di funzioni per serializzare strutture dati, memorizzare i dati serializzati su disco, caricare i dati serializzati dal disco e deserializzare i dati in un nuovo oggetto Python. Ma ci sono anche alcune importanti differenze. Prima di tutto, il formato dati <abbr>JSON</abbr> è basato su testo, non binario. La <a href=http://www.ietf.org/rfc/rfc4627.txt>RFC 4627</a> definisce il formato <abbr>JSON</abbr> e il modo in cui diversi tipi di dato devono essere codificati sotto forma di testo. Per esempio, un valore booleano viene memorizzato come la stringa di cinque caratteri <code>'false'</code> oppure come la stringa di quattro caratteri <code>'true'</code>. Tutti i valori in <abbr>JSON</abbr> sono sensibili alle maiuscole.

<p>Secondo, come con ogni formato basato su testo, c&#8217;è il problema degli spazi bianchi. <abbr>JSON</abbr> consente la presenza di una quantità arbitraria di spazio bianco (spazi, tabulazioni, ritorni a capo e caratteri di fine riga) tra i valori. Questo spazio bianco è &#8220;insignificante&#8221;, nel senso che i codificatori <abbr>JSON</abbr> possono aggiungere tanto spazio bianco quanto desiderano e i decodificatori <abbr>JSON</abbr> sono obbligati a ignorare lo spazio bianco tra i valori. Questo vi permette di &#8220;formattare la stampa&#8221; dei vostri dati <abbr>JSON</abbr>, annidando gradevolmente i valori contenuti in altri valori a differenti livelli di indentazione in modo da poterli leggere in un browser standard o in un editor di testo. Il modulo <code>json</code> di Python è dotato di opzioni per formattare i dati stampati durante la codifica.

<p>Terzo, c&#8217;è l&#8217;eterno problema della codifica di carattere. <abbr>JSON</abbr> codifica i valori come testo semplice, ma come sapete <a href=stringhe.html>il &#8220;testo semplice&#8221; non esiste</a>. <abbr>JSON</abbr> deve essere memorizzato in una codifica Unicode (<abbr>UTF-32</abbr>, <abbr>UTF-16</abbr>, o la codifica predefinita <abbr>UTF-8</abbr>) e la <a href=http://www.ietf.org/rfc/rfc4627.txt>sezione 3 della RFC 4627</a> definisce il modo in cui distinguere quale codifica è stata usata.

<p class=a>&#x2042;

<h2 id=json-dump>Salvare i dati in un file <abbr>JSON</abbr></h2>

<p><abbr>JSON</abbr> somiglia notevolmente a una struttura dati che potreste definire manualmente in JavaScript. Questa somiglianza non è casuale; potete effettivamente usare la funzione JavaScript <code>eval()</code> per &#8220;decodificare&#8221; i dati serializzati in <abbr>JSON</abbr>. (Valgono le solite <a href=uso-avanzato-degli-iteratori.html#eval>avvertenze sugli ingressi non affidabili</a>, ma il punto è che <abbr>JSON</abbr> <em>è</em> codice JavaScript valido.) In quanto tale, <abbr>JSON</abbr> potrebbe già sembrarvi familiare.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>1</samp>
<a><samp class=p>>>> </samp><kbd class=pp>basic_entry = {}</kbd>                                           <span class=u>&#x2460;</span></a>
<samp class=p>>>> </samp><kbd class=pp>basic_entry['id'] = 256</kbd>
<samp class=p>>>> </samp><kbd class=pp>basic_entry['title'] = 'Immersione nella storia, edizione 2009'</kbd>
<samp class=p>>>> </samp><kbd class=pp>basic_entry['tags'] = ('diveintopython', 'docbook', 'html')</kbd>
<samp class=p>>>> </samp><kbd class=pp>basic_entry['published'] = True</kbd>
<samp class=p>>>> </samp><kbd class=pp>basic_entry['comments_link'] = None</kbd>
<samp class=p>>>> </samp><kbd class=pp>import json</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>with open('basic.json', mode='w', encoding='utf-8') as f:</kbd>  <span class=u>&#x2461;</span></a>
<a><samp class=p>... </samp><kbd class=pp>    json.dump(basic_entry, f)</kbd>                              <span class=u>&#x2462;</span></a></pre>
<ol>
<li>Creeremo una nuova struttura dati invece di riutilizzare la struttura dati <var>entry</var> esistente. Più avanti in questo capitolo vedremo cosa succede quando proviamo a codificare strutture dati più complesse in <abbr>JSON</abbr>.
<li><abbr>JSON</abbr> è un formato basato su testo e ciò significa che dovete aprire questo file in modalità testo e specificare una codifica di carattere. Non potete sbagliare se scegliete <abbr>UTF-8</abbr>.
<li>Come il modulo <code>pickle</code>, il modulo <code>json</code> definisce una funzione <code>dump()</code> che prende una struttura dati Python e un oggetto stream aperto in scrittura. La funzione <code>dump()</code> serializza la struttura dati Python e la scrive sull&#8217;oggetto stream. Effettuare questa operazione all&#8217;interno di un&#8217;istruzione <code>with</code> ci garantisce che il file verrà opportunamente chiuso quando avremo finito di lavorare.
</ol>

<p>Quindi che aspetto ha la serializzazione <abbr>JSON</abbr> risultante?

<pre class=screen>
<samp class=p>you@localhost:~/diveintopython3/esempi$ </samp><kbd>cat basic.json</kbd>
<samp>{"published": true, "tags": ["diveintopython", "docbook", "html"], "comments_link": null,
"id": 256, "title": "Immersione nella storia, edizione 2009"}</samp></pre>

<p>Questo è certamente <a href=#debugging>più leggibile di un file pickle</a>. Ma <abbr>JSON</abbr> può contenere una quantità arbitraria di spazio bianco tra i valori, e il modulo <code>json</code> fornisce un modo facile per beneficiare di questa caratteristica creando file <abbr>JSON</abbr> ancora più leggibili.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>1</samp>
<samp class=p>>>> </samp><kbd class=pp>with open('basic-pretty.json', mode='w', encoding='utf-8') as f:</kbd>
<a><samp class=p>... </samp><kbd class=pp>    json.dump(basic_entry, f, <mark style="display:inline">indent=2</mark>)</kbd>                            <span class=u>&#x2460;</span></a></pre>
<ol>
<li>Se passate un parametro <var>indent</var> alla funzione <code>json.dump()</code>, essa renderà il file <abbr>JSON</abbr> risultante più leggibile a spese di una maggiore dimensione del file. Il parametro <var>indent</var> è un intero: 0 significa &#8220;metti ogni valore su una riga separata&#8221;; un numero più grande di 0 significa &#8220;metti ogni valore su una riga separata e usa questo numero di spazi per indentare le strutture dati annidate&#8221;.
</ol>

<p>E questo è il risultato:

<pre class=screen>
<samp class=p>you@localhost:~/diveintopython3/esempi$ </samp><kbd>cat basic-pretty.json</kbd>
<samp>{
  "published": true, 
  "tags": [
    "diveintopython", 
    "docbook", 
    "html"
  ], 
  "comments_link": null, 
  "id": 256, 
  "title": "Immersione nella storia, edizione 2009"
}</samp></pre>

<p class=a>&#x2042;

<h2 id=json-types>Correlare i tipi di dato Python a <abbr>JSON</abbr></h2>

<p>Dato che il formato <abbr>JSON</abbr> non è specifico per Python, ci sono alcuni sfasamenti nella sua copertura dei tipi di dato Python. Alcuni di questi sono semplicemente differenze di nome, ma ci sono due importanti tipi di dato Python che sono completamente assenti. Vedete se riuscite a capire quali sono.

<table>
<tr><th>Note
<th>JSON
<th>Python 3
<tr><th>
<td>oggetto
<td><a href=tipi-di-dato-nativi.html#dictionaries>dizionario</a>
<tr><th>
<td>array
<td><a href=tipi-di-dato-nativi.html#lists>lista</a>
<tr><th>
<td>stringa
<td><a href=stringhe.html#divingin>stringa</a>
<tr><th>
<td>intero
<td><a href=tipi-di-dato-nativi.html#numbers>intero</a>
<tr><th>
<td>numero reale
<td><a href=tipi-di-dato-nativi.html#numbers>numero in virgola mobile</a>
<tr><th>*
<td><code>true</code>
<td><a href=tipi-di-dato-nativi.html#booleans><code>True</code></a>
<tr><th>*
<td><code>false</code>
<td><a href=tipi-di-dato-nativi.html#booleans><code>False</code></a>
<tr><th>*
<td><code>null</code>
<td><code><a href=tipi-di-dato-nativi.html#none>None</a></code>
<tfoot><tr><td colspan=3>* Tutti i valori <abbr>JSON</abbr> sono sensibili alle maiuscole.
</table>

<p>Avete notato cosa manca? Tuple <i class=baa>&amp;</i> byte! <abbr>JSON</abbr> ha un tipo array, che il modulo <code>json</code> mette in correlazione con una lista Python, ma non ha un tipo separato per gli &#8220;array congelati&#8221; (cioè tuple). E mentre <abbr>JSON</abbr> supporta le stringhe piuttosto bene, non ha alcun supporto per gli oggetti <code>bytes</code> o gli array di byte.

<p class=a>&#x2042;

<h2 id=json-unknown-types>Serializzare tipi di dato non supportati da <abbr>JSON</abbr></h2>

<p>Anche se <abbr>JSON</abbr> non ha un supporto built-in per i byte, questo non significa che non potete serializzare gli oggetti <code>bytes</code>. Il modulo <code>json</code> fornisce alcuni agganci estendibili per codificare e decodificare tipi di dato sconosciuti. (Per &#8220;sconosciuti&#8221; intendo &#8220;non definiti in <abbr>JSON</abbr>&#8221;. Ovviamente il modulo <code>json</code> conosce gli array di byte, ma è vincolato dalle limitazioni della specifica <abbr>JSON</abbr>.) Se volete codificare byte o altri tipi di dato che <abbr>JSON</abbr> non supporta nativamente, dovete fornire codificatori e decodificatori personalizzati per quei tipi.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>1</samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry</kbd>                                                 <span class=u>&#x2460;</span></a>
<samp class=pp>{'comments_link': None,
 'internal_id': b'\xDE\xD5\xB4\xF8',
 'title': 'Immersione nella storia, edizione 2009',
 'tags': ('diveintopython', 'docbook', 'html'),
 'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',
 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),
 'published': True}</samp>
<samp class=p>>>> </samp><kbd class=pp>import json</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>with open('entry.json', 'w', encoding='utf-8') as f:</kbd>  <span class=u>&#x2461;</span></a>
<a><samp class=p>... </samp><kbd class=pp>    json.dump(entry, f)</kbd>                               <span class=u>&#x2462;</span></a>
<samp class=p>... </samp>
<samp class=traceback>Traceback (most recent call last):
  File "&lt;stdin>", line 5, in &lt;module>
  File "C:\Python31\lib\json\__init__.py", line 178, in dump
    for chunk in iterable:
  File "C:\Python31\lib\json\encoder.py", line 408, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "C:\Python31\lib\json\encoder.py", line 382, in _iterencode_dict
    for chunk in chunks:
  File "C:\Python31\lib\json\encoder.py", line 416, in _iterencode
    o = _default(o)
  File "C:\Python31\lib\json\encoder.py", line 170, in default
    raise TypeError(repr(o) + " is not JSON serializable")
<mark>TypeError: b'\xDE\xD5\xB4\xF8' is not JSON serializable</mark></samp></pre>
<ol>
<li>Bene, è il momento di rivisitare la struttura dati <var>entry</var>. Ha tutto: un valore booleano, un valore <code>None</code>, una tupla di stringhe, un oggetto <code>bytes</code> e una struttura temporale dal modulo <code>time</code>.
<li>So di averlo già detto, ma vale la pena ripeterlo: <abbr>JSON</abbr> è un formato basato su testo. Aprite sempre i file <abbr>JSON</abbr> in modalità testo con una codifica di carattere <abbr>UTF-8</abbr>.
<li>Be&#8217;, <em>questo</em> non va bene. Cos&#8217;è successo?
</ol>

<p>Ecco cos&#8217;è successo: la funzione <code>json.dump()</code> ha tentato di serializzare l&#8217;oggetto <code>bytes</code> <code>b'\xDE\xD5\xB4\xF8'</code>, ma ha fallito perché <abbr>JSON</abbr> non è dotato di alcun supporto per gli oggetti <code>bytes</code>. Tuttavia, se per voi memorizzare i byte è importante, potete definire il vostro &#8220;mini-formato di serializzazione&#8221;.

<p class=d>[<a href=esempi/customserializer.py>scarica <code>customserializer.py</code></a>]
<pre class=pp><code>
<a>def to_json(python_object):                                                 <span class=u>&#x2460;</span></a>
<a>    if isinstance(python_object, bytes):                                    <span class=u>&#x2461;</span></a>
<a>        return {'__class__': 'bytes',
                '__value__': list(python_object)}                           <span class=u>&#x2462;</span></a>
<a>    raise TypeError(repr(python_object) + ' non è serializzabile in JSON')  <span class=u>&#x2463;</span></a></code></pre>
<ol>
<li>Per definire il vostro &#8220;mini-formato di serializzazione&#8221; per un tipo di dato che <abbr>JSON</abbr> non supporta nativamente, vi basta definire una funzione che prende un oggetto Python come parametro. Questo oggetto Python sarà l&#8217;effettivo oggetto che la funzione <code>json.dump()</code> non è in grado di serializzare da sola&nbsp;&mdash;&nbsp;in questo caso, l&#8217;oggetto <code>b'\xDE\xD5\xB4\xF8'</code> di tipo <code>bytes</code>.
<li>La vostra funzione di serializzazione personalizzata dovrebbe controllare il tipo dell&#8217;oggetto Python che la funzione <code>json.dump()</code> le ha passato. Questo non è strettamente necessario se la vostra funzione serializza un solo tipo di dato, ma chiarisce qual è il caso che la vostra funzione sta affrontando e facilita l&#8217;estensione se più tardi avete bisogno di aggiungere serializzazioni per altri tipi di dato.
<li>In questo caso, ho scelto di convertire un oggetto <code>bytes</code> in un dizionario. La chiave <code>__class__</code> manterrà il tipo di dato originale (sotto forma di stringa, <code>'bytes'</code>) e la chiave <code>__value__</code> manterrà l&#8217;effettivo valore. Naturalmente, questo non può essere un oggetto <code>bytes</code>; il punto è proprio quello di convertire l&#8217;oggetto in qualcosa che possa essere serializzato in <abbr>JSON</abbr>! Un oggetto <code>bytes</code> è solo una sequenza di interi, ognuno compreso nell&#8217;intervallo 0&ndash;255. Possiamo usare la funzione <code>list()</code> per convertire l&#8217;oggetto <code>bytes</code> in una lista di interi, in modo che <code>b'\xDE\xD5\xB4\xF8'</code> diventi <code>[222, 213, 180, 248]</code>. (Fate i calcoli! Funziona! Il byte <code>\xDE</code> in esadecimale corrisponde a 222 in decimale, <code>\xD5</code> corrisponde a 213, e così via.)
<li>Questa riga è importante. La struttura dati che state serializzando potrebbe contenere tipi che né il serializzatore built-in <abbr>JSON</abbr> né il vostro serializzatore personalizzato sono in grado di maneggiare. In questo caso, il vostro serializzatore personalizzato deve sollevare un&#8217;eccezione di tipo <code>TypeError</code> in modo che la funzione <code>json.dump()</code> sappia che il vostro serializzatore personalizzato non ha riconosciuto il tipo di dato.
</ol>

<p>Questo è tutto, non dovete fare altro. In particolare, questa funzione di serializzazione personalizzata <em>restituisce un dizionario Python</em>, non una stringa. Non state completando l&#8217;intera serializzazione in <abbr>JSON</abbr> da soli, ma state solamente effettuando la conversione in un tipo di dato supportato. La funzione <code>json.dump()</code> farà il resto.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>1</samp>
<a><samp class=p>>>> </samp><kbd class=pp>import customserializer</kbd>                                                             <span class=u>&#x2460;</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>with open('entry.json', 'w', encoding='utf-8') as f:</kbd>                                <span class=u>&#x2461;</span></a>
<a><samp class=p>... </samp><kbd class=pp>    json.dump(entry, f, <mark style="display:inline">default=customserializer.to_json</mark>)</kbd>                           <span class=u>&#x2462;</span></a>
<samp class=p>... </samp>
<samp class=traceback>Traceback (most recent call last):
  File "&lt;stdin>", line 9, in &lt;module>
    json.dump(entry, f, default=customserializer.to_json)
  File "C:\Python31\lib\json\__init__.py", line 178, in dump
    for chunk in iterable:
  File "C:\Python31\lib\json\encoder.py", line 408, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "C:\Python31\lib\json\encoder.py", line 382, in _iterencode_dict
    for chunk in chunks:
  File "C:\Python31\lib\json\encoder.py", line 416, in _iterencode
    o = _default(o)
  File "/Users/pilgrim/diveintopython3/esempi/customserializer.py", line 12, in to_json
<a>    raise TypeError(repr(python_object) + ' non è serializzabile in JSON')                 <span class=u>&#x2463;</span></a>
TypeError: time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1) non è serializzabile in JSON</samp></pre>
<ol>
<li>Il modulo <code>customserializer</code> è dove avete appena definito la funzione <code>to_json()</code> dell&#8217;esempio precedente.
<li>Modalità testo, codifica <abbr>UTF-8</abbr>, bla bla bla. (Lo dimenticherete! Me lo dimentico anch&#8217;io, ogni tanto! E tutto funzionerà esattamente fino al momento in cui smetterà di funzionare, e in quel momento tutto si bloccherà in maniera spettacolare.)
<li>Questa è la parte importante: per agganciare la vostra funzione di conversione personalizzata alla funzione <code>json.dump()</code>, passate la vostra funzione alla funzione <code>json.dump()</code> nel parametro <var>default</var>. (Urrà, <a href=il-vostro-primo-programma-python.html#everythingisanobject>ogni cosa in Python è un oggetto</a>!)
<li>Bene, e quindi in realtà non ha funzionato. Ma date un&#8217;occhiata all&#8217;eccezione. La funzione <code>json.dump()</code> non si sta più lamentando di non riuscire a serializzare l&#8217;oggetto <code>bytes</code>. Ora si lamenta di un oggetto completamente differente: l&#8217;oggetto <code>time.struct_time</code>.
</ol>

<p>Sebbene ottenere un&#8217;eccezione differente possa non sembrare un passo avanti, in realtà lo è! Ci vorrà solo un altro ritocco per risolvere il problema.

<pre class=pp><code>
import time

def to_json(python_object):
<a>    if isinstance(python_object, time.struct_time):          <span class=u>&#x2460;</span></a>
<a>        return {'__class__': 'time.asctime',
                '__value__': time.asctime(python_object)}    <span class=u>&#x2461;</span></a>
    if isinstance(python_object, bytes):
        return {'__class__': 'bytes',
                '__value__': list(python_object)}
    raise TypeError(repr(python_object) + ' non è serializzabile in JSON')</code></pre>
<ol>
<li>Aggiungendo codice alla nostra funzione <code>customserializer.to_json()</code> esistente, dobbiamo controllare se l&#8217;oggetto Python (con cui la funzione <code>json.dump()</code> ha dei problemi) è un&#8217;istanza della classe <code>time.struct_time</code>.
<li>Se è così, faremo qualcosa di simile alla conversione che abbiamo compiuto per l&#8217;oggetto <code>bytes</code>: convertiamo l&#8217;oggetto <code>time.struct_time</code> in un dizionario che contiene solo valori serializzabili in formato <abbr>JSON</abbr>. In questo caso, il modo più semplice di convertire una data in un valore serializzabile in formato <abbr>JSON</abbr> è quello di convertirla in una stringa tramite la funzione <code>time.asctime()</code>. La funzione <code>time.asctime()</code> convertirà lo sconveniente oggetto di tipo <code>time.struct_time</code> nella stringa <code>'Fri Mar 27 22:20:42 2009'</code>.
</ol>

<p>Con queste due conversioni personalizzate, l&#8217;intera struttura dati <var>entry</var> dovrebbe venire serializzata in <abbr>JSON</abbr> senza ulteriori problemi.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>1</samp>
<samp class=p>>>> </samp><kbd class=pp>with open('entry.json', 'w', encoding='utf-8') as f:</kbd>
<samp class=p>... </samp><kbd class=pp>    json.dump(entry, f, default=customserializer.to_json)</kbd>
<samp class=p>... </samp></pre>

<pre class=screen>
<samp class=p>you@localhost:~/diveintopython3/esempi$ </samp><kbd>ls -l example.json</kbd>
<samp>-rw-r--r-- 1 you  you  398 Aug  3 13:34 entry.json</samp>
<samp class=p>you@localhost:~/diveintopython3/esempi$ </samp><kbd>cat example.json</kbd>
<samp>{"published_date": {"__class__": "time.asctime", "__value__": "Fri Mar 27 22:20:42 2009"},
"comments_link": null, "internal_id": {"__class__": "bytes", "__value__": [222, 213, 180, 248]},
"tags": ["diveintopython", "docbook", "html"], "title": "Immersione nella storia, edizione 2009",
"article_link": "http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition",
"published": true}</samp></pre>

<p class=a>&#x2042;

<h2 id=json-load>Caricare dati da un file <abbr>JSON</abbr></h2>

<p>Come il modulo <code>pickle</code>, anche il modulo <code>json</code> è dotato di una funzione <code>load()</code> che prende un oggetto stream, legge dati codificati in <abbr>JSON</abbr> dal flusso e crea un nuovo oggetto Python che rispecchia la struttura dati <abbr>JSON</abbr>.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>2</samp>
<a><samp class=p>>>> </samp><kbd class=pp>del entry</kbd>                                             <span class=u>&#x2460;</span></a>
<samp class=p>>>> </samp><kbd class=pp>entry</kbd>
<samp class=traceback>Traceback (most recent call last):
  File "&lt;stdin>", line 1, in &lt;module>
NameError: name 'entry' is not defined</samp>
<samp class=p>>>> </samp><kbd class=pp>import json</kbd>
<samp class=p>>>> </samp><kbd class=pp>with open('entry.json', 'r', encoding='utf-8') as f:</kbd>
<a><samp class=p>... </samp><kbd class=pp>    entry = json.load(f)</kbd>                              <span class=u>&#x2461;</span></a>
<samp class=p>... </samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry</kbd>                                                 <span class=u>&#x2462;</span></a>
<samp class=pp>{'comments_link': None,
 'internal_id': {'__class__': 'bytes', '__value__': [222, 213, 180, 248]},
 'title': 'Immersione nella storia, edizione 2009',
 'tags': ['diveintopython', 'docbook', 'html'],
 'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',
 'published_date': {'__class__': 'time.asctime', '__value__': 'Fri Mar 27 22:20:42 2009'},
 'published': True}</samp></pre>
<ol>
<li>A scopo dimostrativo, spostatevi nella Shell Python n°2 e cancellate la struttura dati <var>entry</var> che avete creato precedentemente in questo capitolo utilizzando il modulo <code>pickle</code>.
<li>Nel caso più semplice, la funzione <code>json.load()</code> opera allo stesso modo della funzione <code>pickle.load()</code>. Passate un oggetto stream e vi viene restituito un nuovo oggetto Python.
<li>Ho una buona e una cattiva notizia. Prima la buona notizia: la funzione <code>json.load()</code> legge con successo il file <code>entry.json</code> che avete creato nella Shell Python n°1 e crea un nuovo oggetto Python che contiene i dati. Ora la cattiva notizia: la funzione non ha ricreato la struttura dati <var>entry</var> originale. I due valori <code>'internal_id'</code> e <code>'published_date'</code> sono stati ricreati sotto forma di dizionari&nbsp;&mdash;&nbsp;nello specifico, dizionari contenenti i valori compatibili con <abbr>JSON</abbr> che avete creato nella funzione di conversione <code>to_json()</code>.
</ol>

<p>La funzione <code>json.load()</code> non sa nulla di qualunque funzione di conversione possiate aver passato a <code>json_dump()</code>. Ciò di cui avete bisogno è l&#8217;opposto della funzione <code>to_json()</code>&nbsp;&mdash;&nbsp;una funzione che prenda un oggetto <abbr>JSON</abbr> proveniente da una conversione personalizzata e lo converta all&#8217;indietro nel tipo di dato Python originale.

<pre class=pp><code># aggiungete questa funzione a customserializer.py
<a>def from_json(json_object):                                   <span class=u>&#x2460;</span></a>
<a>    if '__class__' in json_object:                            <span class=u>&#x2461;</span></a>
        if json_object['__class__'] == 'time.asctime':
<a>            return time.strptime(json_object['__value__'])    <span class=u>&#x2462;</span></a>
        if json_object['__class__'] == 'bytes':
<a>            return bytes(json_object['__value__'])            <span class=u>&#x2463;</span></a>
    return json_object</code></pre>
<ol>
<li>Anche questa funzione di conversione prende un parametro e restituisce un valore. Ma il parametro che accetta non è una stringa, bensì un oggetto Python&nbsp;&mdash;&nbsp;il risultato della deserializzazione in Python di una stringa codificata in <abbr>JSON</abbr>.
<li>Tutto quello che vi serve è controllare se questo oggetto contiene la chiave <code>'__class__'</code> che la funzione <code>to_json()</code> aveva creato. Se è così, il valore della chiave <code>'__class__'</code> vi dirà come decodificare il valore nel suo tipo di dato Python originale.
<li>Per decodificare la stringa restituita dalla funzione <code>time.asctime()</code> che contiene la data, usate la funzione <code>time.strptime()</code>. Questa funzione prende una stringa che contiene una data formattata (in un formato personalizzabile, ma usa lo stesso formato predefinito della funzione <code>time.asctime()</code>) e restituisce un oggetto <code>time.struct_time</code>.
<li>Per convertire una lista di interi in un oggetto <code>bytes</code>, potete usare la funzione <code>bytes()</code>.
</ol>

<p>E questo è tutto: c&#8217;erano solo due tipi di dato gestiti nella funzione <code>to_json()</code> e ora quei due tipi di dato sono gestiti anche nella funzione <code>from_json()</code>. Questo è il risultato:

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>2</samp>
<samp class=p>>>> </samp><kbd class=pp>import customserializer</kbd>
<samp class=p>>>> </samp><kbd class=pp>with open('entry.json', 'r', encoding='utf-8') as f:</kbd>
<a><samp class=p>... </samp><kbd class=pp>    entry = json.load(f, object_hook=customserializer.from_json)</kbd>  <span class=u>&#x2460;</span></a>
<samp class=p>... </samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry</kbd>                                                             <span class=u>&#x2461;</span></a>
<samp class=pp>{'comments_link': None,
 'internal_id': b'\xDE\xD5\xB4\xF8',
 'title': 'Immersione nella storia, edizione 2009',
 'tags': ['diveintopython', 'docbook', 'html'],
 'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',
 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),
 'published': True}</samp></pre>
<ol>
<li>Per agganciare la funzione <code>from_json()</code> al processo di deserializzazione, passatela come parametro <var>object_hook</var> alla funzione <code>json.load()</code>. Funzioni che accettano funzioni come parametri: è così comodo!
<li>Ora la struttura dati <var>entry</var> contiene una chiave <code>'internal_id'</code> il cui valore è un oggetto <code>bytes</code>. Contiene anche una chiave <code>'published_date'</code> il cui valore è un oggetto <code>time.struct_time</code>.
</ol>

<p>C&#8217;è ancora un ultimo difetto, però.

<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>shell</kbd>
<samp class=pp>1</samp>
<samp class=p>>>> </samp><kbd class=pp>import customserializer</kbd>
<samp class=p>>>> </samp><kbd class=pp>with open('entry.json', 'r', encoding='utf-8') as f:</kbd>
<samp class=p>... </samp><kbd class=pp>    entry2 = json.load(f, object_hook=customserializer.from_json)</kbd>
<samp class=p>... </samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry2 == entry</kbd>                                                    <span class=u>&#x2460;</span></a>
<samp class=pp>False</samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry['tags']</kbd>                                                      <span class=u>&#x2461;</span></a>
<samp class=pp>('diveintopython', 'docbook', 'html')</samp>
<a><samp class=p>>>> </samp><kbd class=pp>entry2['tags']</kbd>                                                     <span class=u>&#x2462;</span></a>
<samp class=pp>['diveintopython', 'docbook', 'html']</samp></pre>
<ol>
<li>Anche dopo aver agganciato la funzione <code>to_json()</code> al processo di serializzazione e aver agganciato la funzione <code>from_json()</code> a quello di deserializzazione, non abbiamo ancora ricreato una riproduzione perfetta della struttura dati originale. Come mai?
<li>Nella struttura dati <var>entry</var> originale, il valore della chiave <code>'tags'</code> era una tupla di tre stringhe.
<li>Ma nella struttura dati <var>entry2</var> il valore della chiave <code>'tags'</code> è diventato una <em>lista</em> di tre stringhe a seguito del viaggio di andata e ritorno tra serializzazione e deserializzazione. <abbr>JSON</abbr> non distingue le tuple dalle liste; usa gli array come singolo tipo di dato simile alle liste, così il modulo <code>json</code> converte silenziosamente sia le tuple che le liste in array <abbr>JSON</abbr> durante la serializzazione. La maggior parte delle volte potete ignorare la differenza tra tuple e liste, ma questo sfasamento è una cosa da tenere presente quando lavorate con il modulo <code>json</code>.
</ol>

<h2 id=furtherreading>Letture di approfondimento</h2>

<blockquote class=note>
<p><span class=u>&#x261E;</span>Molti articoli sul modulo <code>pickle</code> fanno riferimento a <code>cPickle</code>. In Python 2, c&#8217;erano due implementazioni del modulo <code>pickle</code>, una scritta in Python e una scritta in C (ma invocabile da Python). In Python 3, <a href=convertire-codice-verso-python-3-con-2to3.html#othermodules>questi due moduli sono stati unificati</a>, quindi dovreste sempre scrivere semplicemente <code>import pickle</code>. Potreste trovare utili questi articoli, ma dovreste ignorare il riferimento ormai obsoleto a <code>cPickle</code>.
</blockquote>

<p>Sulla serializzazione tramite il modulo <code>pickle</code>:

<ul>
<li><a href=http://docs.python.org/3.1/library/pickle.html>Il modulo <code>pickle</code></a>
<li><a href=http://www.doughellmann.com/PyMOTW/pickle/><code>pickle</code> e <code>cPickle</code>&nbsp;&mdash;&nbsp;la serializzazione di oggetti Python</a>
<li><a href=http://wiki.python.org/moin/UsingPickle>Usare <code>pickle</code></a>
<li><a href=http://www.ibm.com/developerworks/library/l-pypers.html>La gestione della persistenza in Python</a>
</ul>

<p>Su <abbr>JSON</abbr> e il modulo <code>json</code>:

<ul>
<li><a href=http://www.doughellmann.com/PyMOTW/json/><code>json</code>&nbsp;&mdash;&nbsp;il serializzatore in JavaScript Object Notation</a>
<li><a href=http://blog.quaternio.net/2009/07/16/json-encoding-and-decoding-with-custom-objects-in-python/>La codifica e la decodifica JSON con oggetti personalizzati in Python</a>
</ul>

<p>Sulla estendibilità del protocollo pickle:

<ul>
<li><a href=http://docs.python.org/3.1/library/pickle.html#pickling-class-instances>Serializzare istanze di classi</a>
<li><a href=http://docs.python.org/3.1/library/pickle.html#persistence-of-external-objects>La persistenza di oggetti esterni</a>
<li><a href=http://docs.python.org/3.1/library/pickle.html#handling-stateful-objects>Gestire oggetti con stato</a>
</ul>

<p class=v><a rel=prev href=xml.html title='indietro a &#8220;XML&#8221;'><span class=u>&#x261C;</span></a> <a rel=next href=servizi-web-http.html title='avanti a &#8220;Servizi web HTTP&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;10 <a href=about.html>Mark Pilgrim</a><br>
&copy; 2009&ndash;10 <a href=informazioni-sulla-traduzione.html>Giulio Piancastelli</a> per la traduzione italiana
<script src=j/jquery.js></script>
<script src=j/prettify.js></script>
<script src=j/dip3.js></script>