Capitolo 13. Ancora sulle liste

Abbiamo già esaminato le liste e come vengono utilizzate. Ora che avete un background più corposo entrerò più in dettaglio. Innanzitutto scopriremo altri modi per estrarre gli elementi dalle liste, quindi analizzeremo un metodo per copiarle.

Questi sono alcuni esempi dell'utilizzo degli indici per accedere ai singoli elementi di una lista:

>>> list = ['zero','one','two','three','four','five']
>>> list[0]
'zero'
>>> list[4]
'four'
>>> list[5]
'five'
Questi esempi dovrebbero esservi familiari. Se volete estrarre il primo elemento di una lista dovrete richiamare l'elemento con indice 0, il secondo elemento avrà indice 1 e così via attraverso la lista. Come fare se volete l'ultimo elemento della lista ? Una via potrebbe essere quella di utilizzare la funzione len all'interno delle parentesi quadre: list[len(list)-1]. len ritorna sempre l'ultimo indice + 1. Nello stesso modo l'istruzione per avere il penultimo elemento della lista sarà list[len(list)-2]. Esiste una via più semplice: in Python l'ultimo elemento è sempre indicizzato come -1, il penultimo come -2 e così via. Ecco un'esempio:
>>> list[len(list)-1]
'five'
>>> list[len(list)-2]
'four'
>>> list[-1]
'five'
>>> list[-2]
'four'
>>> list[-6]
'zero'
Così ogni elemento in una lista può essere indicizzato in due modi differenti, dall'inizio della lista e dalla fine della lista.

Un'altra via molto utile per estrarre elementi da una lista sono le sezioni. Ecco un'altro esempio:

>>> list = [0,'Fred',2,'S.P.A.M.','Stocking',42,"Jack","Jill"]
>>> list[0]
0
>>> list[7]
'Jill'
>>> list[0:8]
[0, 'Fred', 2, 'S.P.A.M.', 'Stocking', 42, 'Jack', 'Jill']
>>> list[2:4]
[2, 'S.P.A.M.']
>>> list[4:7]
['Stocking', 42, 'Jack']
>>> list[1:5]
['Fred', 2, 'S.P.A.M.', 'Stocking']
Le sezioni sono utilizzate per estrarre parti di liste. La sintassi per estrarre sezioni è list[primo_indice:ultimo_indice]. La sezione selezionata va da primo_indice all'indice prima di ultimo_indice. E' possibile utilizzare entrambi i metodi di indicizzazione:
>>> list[-4:-2]
['Stocking', 42]
>>> list[-4]
'Stocking'
>>> list[-4:6]
['Stocking', 42]
Un'altro trucco con le sezioni è non specificare l'indice. Se il primo indice non viene specificato Python assumerà il primo indice della lista come indice da cui partire: Ecco un'altro esempio:
>>> list[:2]
[0, 'Fred']
>>> list[-2:]
['Jack', 'Jill']
>>> list[:3]
[0, 'Fred', 2]
>>> list[:-5]
[0, 'Fred', 2]
Ancora un'altro programma d'esempio:
poem = \
["<B>","Jack","and","Jill","</B>","went","up","the","hill","to","<B>",\
"fetch","a","pail","of","</B>","water.","Jack","fell","<B>","down","and",\
"broke","</B>","his","crown","and","<B>","Jill","came","</B>","tumbling",\
"after"]

def get_bolds(list):
        true = 1
        false = 0
        ## is_bold tells whether or not the we are currently looking at
        ## a bold section of text.
        is_bold = false
        ## start_block is the index of the start of either an unbolded
        ## segment of text or a bolded segment.
        start_block = 0
        for index in range(len(list)):
                ##Handle a starting of bold text
                if list[index] == "<B>":
                        if is_bold:
                                print "Error:  Extra Bold"
                        ##print "Not Bold:",list[start_block:index]
                        is_bold = true
                        start_block = index+1
                ##Handle end of bold text
                if list[index] == "</B>":
                        if not is_bold:
                                print "Error: Extra Close Bold"
                        print "Bold [",start_block,":",index,"] ",\
                        list[start_block:index]
                        is_bold = false
                        start_block = index+1

get_bolds(poem)
E questo è l'output:
Bold [ 1 : 4 ]  ['Jack', 'and', 'Jill']
Bold [ 11 : 15 ]  ['fetch', 'a', 'pail', 'of']
Bold [ 20 : 23 ]  ['down', 'and', 'broke']
Bold [ 28 : 30 ]  ['Jill', 'came']
La funzione get_bold scorre per una lista dividendola in parole e chiamate. Le chiamate che cerca sono <B> che inizia il testo in grassetto e <\B> che lo termina.

La prossima funzione delle liste è copiarle:

>>> a = [1,2,3]
>>> b = a
>>> print b
[1, 2, 3]
>>> b[1] = 10
>>> print b
[1, 10, 3]
>>> print a
[1, 10, 3]
Questo è probabilmente sorprendente in quanto una modifica a b modifica anche a. Questo grazie all'istruzione b = a che restituisce b come riferimento ad a. Significa che b non è altro che un'altro nome per riferirsi ad a, il risultato è che una modifica a b è una modifica ad a. Ciò nonostante alcune tipologie di assegnamento non significano la creazione di un doppio riferimento:
>>> a = [1,2,3]
>>> b = a*2
>>> print a
[1, 2, 3]
>>> print b
[1, 2, 3, 1, 2, 3]
>>> a[1] = 10
>>> print a
[1, 10, 3]
>>> print b
[1, 2, 3, 1, 2, 3]
In questo caso b non è un riferimento ad a in quanto l'istruzione a*2 crea una nuova lista; b quindi si riferisce ad a*2 e non ad a. Tutti gli assegnamenti creano un riferimento. Quando passate una lista come argomento ad una funzione create un riferimento. La maggior parte delle volte non dovrete preoccuparvi di creare un riferimento anziché una copia. Ciò nonostante, quando dovete modificare una lista senza cambiarne un'altra assegnata ad un nome differente, dovete assicurarvi di aver creato una copia e non un riferimento. Esistono diversi modi per copiare una lista. Il modo più semplice è usare le sezioni:
>>> a = [1,2,3]
>>> b = a[:]
>>> b[1] = 10
>>> print a
[1, 2, 3]
>>> print b
[1, 10, 3]
In questo modo si può copiare una lista, ma qualsiasi sottolista creata in seguito si riferirà alla rispettiva sottolista della lista originale. Potete ovviare al problema copiando anche le sottoliste usando la funzione deepcopy del modulo copy :
>>> import copy
>>> a = [[1,2,3],[4,5,6]]
>>> b = a[:]
>>> c = copy.deepcopy(a)
>>> b[0][1] = 10
>>> c[1][1] = 12
>>> print a
[[1, 10, 3], [4, 5, 6]]
>>> print b
[[1, 10, 3], [4, 5, 6]]
>>> print c
[[1, 2, 3], [4, 12, 6]]
Innanzitutto notate che a è una lista di liste. L'istruzione b[0][1] = 10 cambia sia la lista b che la lista a, c invece resta uguale. Questo accade perché b continua ad essere un riferimento ad a se vengono utilizzate le sezioni; c invece è una copia ottenuta grazie alla funzione deepcopy.

Dovrete preoccuparvi dei riferimenti solamente quando utilizzate dizionari e liste. Numeri e stringhe creano dei riferimenti ma quando vengono modificati creano una copia, quindi non potrete mai modificarli inaspettatamente.

Adesso vi starete probabilmente chiedendo perché vengono usati i riferimenti. La ragione basilare è per la loro velocità. E' molto più veloce fare un riferimento a una lista di migliaia di riferimenti che copiarli tutti. Un'altra ragione è che permettono di avere una funzione che modifichi una lista o un dizionario. Tenetene quindi conto se vi troverete ad avere strani errori in relazione a dati modificati quando quest'ultimi non avrebbero dovuto subire alcun cambiamento.