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 vedremo 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 più uno. Allo 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'altro 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'altro metodo molto funzionale per estrarre elementi da una lista sono le sezioni. Ecco un'altro esempio per avere un'idea di come usarle:

>>> 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. È 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. Se il successivo indice non è specificato, si intenderà tutto il resto della lista. Ecco altri esempi:
>>> list[:2]
[0, 'Fred']
>>> list[-2:]
['Jack', 'Jill']
>>> list[:3]
[0, 'Fred', 2]
>>> list[:-5] 
[0, 'Fred', 2]
Ancora un'altro programma d'esempio (copiate ed incollate, se volete, la definizione di poem):
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
        ## Dichiara is_bold come falso, quindi stiamo guardando una
        ## porzione di testo non in grassetto.
        is_bold = false
        ## start_block rappresenta l'indice delle porzioni di testo in
        ## grassetto.
        start_block = 0
        for index in range(len(list)):
                ## TAG che rappresenta l'inizio del grassetto.
                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
                ## TAG che rappresenta la fine del grassetto.
                ## Ricordate che l'ultimo numero in una sezione
                ## rappresenta l'indice e dopo l'ultimo indice usato.
                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 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 funzione get_bold ricerca l'inizio e la fine delle chiamate.

La prossima funzione delle liste è la copia. Se volete, potete provare questo semplice esempio:

>>> a = [1,2,3]
>>> b = a
>>> print b
[1, 2, 3]
>>> b[1] = 10
>>> print b
[1, 10, 3]
>>> print a
[1, 10, 3]
Probabilmente vi sorprenderà 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 nome per una lista:
>>> 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. Tuttavia, 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 poiché creano sempre una nuova lista perfino se la sezione comprende tutta la lista:

>>> a = [1,2,3]
>>> b = a[:]
>>> b[1] = 10
>>> print a
[1, 2, 3]
>>> print b
[1, 10, 3]

La sezione [:] crea una nuova copia della lista. 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, mentre c rimane inalterata. Questo accade perché b continua ad essere un riferimento ad a se vengono utilizzate le sezioni; c invece è una copia completa, ottenuta grazie alla funzione deepcopy.

Perciò dovrete preoccuparvi dei riferimenti ogni volta che userete la funzione ``=''? La buona notizia è che dovrete occuparvi 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 motivazione, sostanzialmente, è la loro velocità. È molto più veloce fare un riferimento ad 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. Tenete in considerazione tutto questo se vi troverete ad avere strani errori in relazione a dati modificati quando quest'ultimi non avrebbero dovuto subire alcun cambiamento.