Vai al contenuto

Capitolo 7 · Liste, tuple, set e dizionari

Cosa impari

  • a creare una lista, a leggerne gli elementi con gli indici e a scorrerla con un ciclo for;
  • a modificare una lista: concatenazione, slicing, operatore in e metodi append, index, insert, sort, remove, reverse, l'istruzione del e le funzioni min e max;
  • a copiare una lista, a calcolare totale e media, a passare liste alle funzioni e a salvarle su file;
  • a usare le list comprehension e le liste bidimensionali (liste di liste);
  • che cosa sono tuple, set e dizionari e quando conviene usarli;
  • a rappresentare una tabella di dati con liste parallele, lista di liste o lista di dizionari, come chiedono le prove d'esame.

Finora ogni variabile conteneva un solo valore: un numero, una stringa. Pensa però ai voti di tutti gli studenti di un corso, o all'elenco dei film di un cinema: usare una variabile per ogni dato sarebbe impossibile. In questo capitolo impari a usare le strutture dati di Python, che raccolgono molti valori sotto un solo nome.

Sequenze

Una sequenza è un oggetto che contiene più dati, memorizzati uno dopo l'altro. Puoi esaminare gli elementi di una sequenza e, in certi casi, modificarli. Le stringhe del capitolo 3.5 sono già un esempio di sequenza (di caratteri).

Python offre diversi tipi di sequenze. In questo capitolo vediamo le liste e le tuple. La differenza principale è una sola:

  • una lista è mutabile: puoi cambiarne il contenuto mentre il programma è in esecuzione;
  • una tupla è immutabile: dopo averla creata non puoi più modificarla.

Introduzione alle liste

Una lista è un oggetto che contiene molti dati. Ogni dato si chiama elemento. Per creare una lista scrivi gli elementi tra parentesi quadre, separati da virgole:

nome_lista = [elemento1, elemento2, elemento3]

Una lista può contenere dati di tipo diverso: numeri, stringhe, valori booleani, anche altre liste. Le liste sono dinamiche: puoi aggiungere ed eliminare elementi durante l'esecuzione. Per vedere tutta la lista basta passarla a print.

even_numbers = [2, 4, 6, 8, 10]
names = ['Luca', 'Sara', 'Marco', 'Giulia']
info = ['Alicia', 27, 1550.87]     # tipi diversi nella stessa lista
print(even_numbers)
print(names)
print(info)

La funzione list()

La funzione list() converte altri dati in una lista. Un caso molto comune è con range: range(5) produce i valori 0, 1, 2, 3, 4 e list li raccoglie in una lista.

numbers = list(range(5))
print(numbers)              # [0, 1, 2, 3, 4]
numbers = list(range(1, 10, 2))
print(numbers)              # [1, 3, 5, 7, 9]: da 1 a 10 escluso, passo 2

L'operatore di ripetizione *

Come per le stringhe, se a sinistra di * c'è una lista e a destra un intero, * diventa l'operatore di ripetizione: crea più copie della lista e le unisce in un'unica lista. È il modo più veloce per creare una lista di zeri.

numbers = [0] * 5
print(numbers)              # [0, 0, 0, 0, 0]
numbers = [1, 2, 3] * 3
print(numbers)              # [1, 2, 3, 1, 2, 3, 1, 2, 3]

Scorrere una lista con for

Il ciclo for funziona con le liste come con range:

for variabile in lista:
    istruzione

A ogni giro, variabile contiene un elemento della lista, partendo dal primo.

numbers = [1, 2, 3, 4]
for num in numbers:
    print(num)

La lista ha quattro elementi, quindi il ciclo fa quattro giri: num vale 1, poi 2, poi 3, poi 4.

Indici e lunghezza

Ogni elemento di una lista ha un indice, cioè la sua posizione. Come nelle stringhe, il primo elemento ha indice 0 e l'ultimo ha indice n − 1, dove n è il numero di elementi.

my_list = [10, 20, 30, 40]
print(my_list[0], my_list[1], my_list[2], my_list[3])

# Stessa cosa con un ciclo while e un indice
index = 0
while index < 4:
    print(my_list[index])
    index += 1

Puoi usare anche indici negativi, che contano dalla fine: -1 è l'ultimo elemento, -2 il penultimo e così via. Python somma l'indice negativo alla lunghezza della lista: in una lista di 4 elementi, -1 diventa 4 − 1 = 3.

my_list = [10, 20, 30, 40]
print(my_list[-1], my_list[-2], my_list[-3], my_list[-4])   # 40 30 20 10

Se usi un indice che non esiste, Python solleva un IndexError. L'esempio che segue mostra apposta questo errore: il ciclo arriva fino a index = 4, ma l'ultimo indice valido è 3.

my_list = [10, 20, 30, 40]
index = 0
while index < 5:
    print(my_list[index])    # al quinto giro: IndexError
    index += 1

La funzione len

Per non sbagliare l'ultimo indice, usa la funzione len: restituisce il numero di elementi di una sequenza. Così il ciclo funziona con liste di qualsiasi lunghezza.

my_list = [10, 20, 30, 40]
size = len(my_list)
print('Elementi:', size)

index = 0
while index < len(my_list):
    print(my_list[index])
    index += 1

Ciclo for sugli indici

range(len(lista)) produce proprio gli indici validi della lista: con 4 elementi produce 0, 1, 2, 3. Ti serve quando, oltre all'elemento, ti interessa anche la sua posizione.

names = ['Luca', 'Sara', 'Marco', 'Giulia']
for index in range(len(names)):
    print(index, names[index])

Le liste sono mutabili

A differenza delle stringhe, le liste sono mutabili: puoi cambiare un elemento assegnando un nuovo valore alla sua posizione.

numbers = [1, 2, 3, 4, 5]
print(numbers)          # [1, 2, 3, 4, 5]
numbers[0] = 99
print(numbers)          # [99, 2, 3, 4, 5]

Puoi modificare solo posizioni che esistono già. In una lista di 5 elementi l'ultimo indice è 4: scrivere in numbers[5] dà IndexError. L'esempio che segue mostra apposta questo errore.

numbers = [1, 2, 3, 4, 5]
numbers[5] = 99         # IndexError: list assignment index out of range

Un modo tipico di riempire una lista: prima la crei della lunghezza giusta con *, poi assegni un valore a ogni posizione.

numbers = [0] * 5
for index in range(len(numbers)):
    numbers[index] = 99
print(numbers)          # [99, 99, 99, 99, 99]

Concatenare liste

Concatenare significa unire. L'operatore + tra due liste crea una nuova lista con gli elementi della prima seguiti da quelli della seconda. Si possono concatenare solo liste con altre liste: [1, 2] + 3 dà un TypeError.

list1 = [1, 2, 3, 4]
list2 = [5, 6, 7, 8]
list3 = list1 + list2
print(list3)            # [1, 2, 3, 4, 5, 6, 7, 8]

nomi_a = ['Luca', 'Sara', 'Marco']
nomi_b = ['Giulia', 'Paola', 'Andrea']
tutti = nomi_a + nomi_b
print(tutti)

Con l'operatore += aggiungi una lista in fondo a un'altra. La lista di sinistra cambia, quella di destra resta uguale.

list1 = [1, 2, 3, 4]
list2 = [5, 6, 7, 8]
list1 += list2
print(list1)            # [1, 2, 3, 4, 5, 6, 7, 8]
print(list2)            # [5, 6, 7, 8]: non cambia

Slicing: porzioni di lista

Con un indice prendi un elemento. Con lo slicing prendi una porzione (slice), cioè un gruppo di elementi. La sintassi è la stessa delle stringhe:

nome_lista[inizio:fine]

Il risultato è una nuova lista con una copia degli elementi da inizio (incluso) a fine (escluso).

days = ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica']
mid_days = days[2:5]
print(mid_days)         # ['Mercoledì', 'Giovedì', 'Venerdì']

Come nelle stringhe, gli indici sono facoltativi e puoi aggiungere un passo:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(numbers[1:3])     # [2, 3]
print(numbers[:3])      # [1, 2, 3]: senza inizio si parte da 0
print(numbers[2:])      # [3, 4, ..., 10]: senza fine si arriva in fondo
print(numbers[:])       # copia di tutta la lista
print(numbers[1:8:2])   # [2, 4, 6, 8]: passo 2
print(numbers[-5:])     # [6, 7, 8, 9, 10]: gli ultimi cinque

Lo slicing non dà errori di indice

Con lo slicing gli indici fuori dalla lista non sollevano eccezioni:

  • se fine va oltre la fine della lista, Python usa la lunghezza della lista;
  • se inizio è prima dell'inizio, Python usa 0;
  • se inizio è maggiore di fine, ottieni una lista vuota [].

Cercare un elemento con in

L'operatore in dice se un elemento è presente in una lista: elemento in lista vale True oppure False. L'operatore not in fa il contrario.

# Codici dei prodotti in magazzino
prod_nums = ['V475', 'F987', 'Q143', 'R688']
search = input('Inserire un codice prodotto: ')
if search in prod_nums:
    print(f'{search} è stato trovato nella lista.')
else:
    print(f'{search} non è stato trovato nella lista.')

Prova con Q143 (trovato) e con B000 (non trovato). Lo stesso controllo si può scrivere con not in, invertendo i due rami:

names = ['Luca', 'Sara', 'Marco', 'Giulia']
if 'Giulia' not in names:
    print('Impossibile trovare Giulia.')
else:
    print('Gruppo di Giulia:')
    print(names)

Metodi delle liste e funzioni utili

Le liste hanno molti metodi (ricorda: si chiamano con il punto, lista.metodo()). Servono ad aggiungere ed eliminare elementi e a cambiarne l'ordine. A differenza dei metodi delle stringhe, questi metodi modificano la lista stessa.

Metodo / istruzione Che cosa fa
lista.append(x) aggiunge x in fondo alla lista
lista.index(x) restituisce l'indice del primo elemento uguale a x (ValueError se non c'è)
lista.insert(i, x) inserisce x nella posizione i; gli elementi successivi si spostano avanti
lista.sort() ordina la lista in ordine crescente
lista.remove(x) elimina il primo elemento uguale a x (ValueError se non c'è)
lista.reverse() inverte l'ordine degli elementi
del lista[i] elimina l'elemento in posizione i
min(lista), max(lista) restituiscono il valore più piccolo e più grande

append: aggiungere in fondo

Il metodo append aggiunge un elemento alla fine della lista. È il modo normale di costruire una lista partendo da una lista vuota [], quando non sai in anticipo quanti elementi ci saranno.

name_list = []          # lista vuota
again = 's'
while again == 's':
    name = input('Inserisci un nome: ')
    name_list.append(name)
    print('Desideri aggiungere un altro nome?')
    again = input('s = sì, qualsiasi altra cosa = no: ')
    print()

print('Ecco i nomi che hai inserito.')
for name in name_list:
    print(name)

index: trovare la posizione

A volte non basta sapere se un elemento c'è: serve sapere dove si trova. Il metodo index restituisce l'indice del primo elemento uguale all'argomento. Se l'elemento non c'è, solleva un ValueError: per questo l'esempio usa try/except (capitolo 6).

food = ['Pizza', 'Hamburger', 'Patatine']
print('Ecco gli elementi della lista food:')
print(food)
item = input('Quale elemento devo modificare? ')
try:
    item_index = food.index(item)
    new_item = input('Inserisci il nuovo valore: ')
    food[item_index] = new_item
    print('Ecco la lista modificata:')
    print(food)
except ValueError:
    print('Elemento non trovato nella lista.')

Prova a sostituire Hamburger con Cetriolini.

insert: inserire in una posizione

Il metodo insert riceve due argomenti: l'indice in cui inserire e l'elemento. Gli elementi che seguono si spostano di una posizione. Se l'indice è troppo grande non c'è errore: l'elemento finisce in fondo.

names = ['Luca', 'Giulia', 'Marco']
print("Ecco la lista prima dell'inserimento:")
print(names)
names.insert(0, 'Sara')
print("Ecco la lista dopo l'inserimento:")
print(names)            # ['Sara', 'Luca', 'Giulia', 'Marco']

sort e reverse: cambiare l'ordine

sort ordina gli elementi dal più piccolo al più grande; le stringhe vengono messe in ordine alfabetico. reverse inverte semplicemente l'ordine attuale.

my_list = [9, 1, 0, 2, 8, 6, 7, 4, 5, 3]
print('Sequenza originale:', my_list)
my_list.sort()
print('Sequenza ordinata:', my_list)

my_list = ['beta', 'alpha', 'delta', 'gamma']
my_list.sort()
print('Sequenza ordinata:', my_list)

my_list = [1, 2, 3, 4, 5]
my_list.reverse()
print('Ordine invertito:', my_list)     # [5, 4, 3, 2, 1]

remove e del: eliminare elementi

remove elimina il primo elemento uguale a un valore. Gli elementi successivi si spostano indietro di una posizione e la lista si accorcia di uno. Se il valore non c'è, solleva un ValueError.

food = ['Pizza', 'Hamburger', 'Patatine']
print('Ecco gli elementi della lista food:')
print(food)
item = input('Quale elemento devo eliminare? ')
try:
    food.remove(item)
    print('Ecco la lista modificata:')
    print(food)
except ValueError:
    print('Elemento non trovato nella lista.')

L'istruzione del invece elimina l'elemento che si trova in una certa posizione.

my_list = [1, 2, 3, 4, 5]
print("Prima dell'eliminazione:", my_list)
del my_list[2]
print("Dopo l'eliminazione:", my_list)    # [1, 2, 4, 5]

remove o del?

Usa remove quando conosci il valore da togliere (food.remove('Pizza')). Usa del quando conosci la posizione (del food[0]).

min e max

Le funzioni integrate min e max ricevono una sequenza e restituiscono il valore più basso e quello più alto. Nota che sono funzioni, non metodi: si scrive min(lista), non lista.min().

my_list = [5, 4, 3, 2, 50, 40, 30]
print('Il valore più basso è', min(my_list))    # 2
print('Il valore più alto è', max(my_list))     # 50

append, non assegnazione a un indice che non c'è

Se names = [], l'istruzione names[0] = 'Sara' dà IndexError: la lista è vuota, la posizione 0 non esiste ancora. Per aggiungere un elemento usa names.append('Sara').

Copiare una lista

Attenzione: se scrivi list2 = list1, non crei una seconda lista. Le due variabili fanno riferimento alla stessa lista in memoria. Se modifichi la lista tramite una variabile, la modifica si vede anche dall'altra.

list1 = [1, 2, 3, 4]
list2 = list1           # stessa lista, due nomi
list1[0] = 99
print(list1)            # [99, 2, 3, 4]
print(list2)            # [99, 2, 3, 4]: è cambiata anche lei!

Per avere due liste uguali ma separate devi copiare gli elementi. Puoi farlo con un ciclo e append, oppure, in modo più semplice, concatenando la lista a una lista vuota.

list1 = [1, 2, 3, 4]

# Copia con un ciclo
list2 = []
for item in list1:
    list2.append(item)

# Copia con la concatenazione
list3 = [] + list1

list1[0] = 99
print(list1)            # [99, 2, 3, 4]
print(list2)            # [1, 2, 3, 4]: non cambia
print(list3)            # [1, 2, 3, 4]: non cambia

Anche lo slicing list1[:] restituisce una copia.

Elaborare le liste

Esempio: la paga dei baristi

Il titolare di un bar ha alcuni baristi, tutti con la stessa paga oraria. Vuole inserire le ore lavorate da ognuno e vedere la paga lorda di ciascuno. Il programma:

  1. per ogni dipendente legge le ore lavorate e le memorizza in un elemento della lista hours;
  2. per ogni elemento della lista calcola la paga lorda e la mostra.
NUM_EMPLOYEES = 3       # numero di dipendenti

def main():
    # Crea una lista con un elemento per ogni dipendente
    hours = [0] * NUM_EMPLOYEES

    # Legge le ore lavorate da ogni dipendente
    for index in range(NUM_EMPLOYEES):
        hours[index] = float(input(f'Ore lavorate dal dipendente {index + 1}: '))

    pay_rate = float(input('Paga oraria: '))

    # Mostra la paga lorda di ogni dipendente
    for index in range(NUM_EMPLOYEES):
        gross_pay = hours[index] * pay_rate
        print(f'Paga lorda del dipendente {index + 1}: €{gross_pay:,.2f}')

main()

Se il bar assume altri baristi, basta cambiare la costante NUM_EMPLOYEES: la lunghezza della lista e il numero di giri dei due cicli si aggiornano da soli.

Sommare i valori

Per calcolare il totale dei valori di una lista usi un ciclo con un accumulatore: parti da 0 e aggiungi ogni elemento.

numbers = [2, 4, 6, 8, 10]
total = 0
for value in numbers:
    total += value
print('Il totale degli elementi è', total)     # 30

Calcolare la media

Per la media calcoli prima il totale, poi lo dividi per il numero di elementi, cioè len(lista).

scores = [2.5, 7.3, 6.5, 4.0, 5.2]
total = 0.0
for value in scores:
    total += value
average = total / len(scores)
print('La media degli elementi è', average)   # 5.1

Le scorciatoie sum e len

Python ha anche la funzione integrata sum(lista), che restituisce direttamente il totale. Quindi la media si può scrivere sum(scores) / len(scores). All'esame va bene anche il ciclo con l'accumulatore: l'importante è capire che cosa succede.

Passare una lista a una funzione

Una lista si può passare come argomento a una funzione, come qualsiasi altro valore. Così puoi chiudere in una funzione un'operazione che fai spesso sulle liste.

def main():
    numbers = [2, 4, 6, 8, 10]
    print('Il totale è', get_total(numbers))

# Riceve una lista e restituisce il totale dei suoi elementi
def get_total(value_list):
    total = 0
    for num in value_list:
        total += num
    return total

main()

La funzione può modificare la lista

Il parametro fa riferimento alla stessa lista dell'argomento (come list2 = list1). Se la funzione esegue ad esempio value_list.append(0), la lista del chiamante cambia davvero.

Restituire una lista da una funzione

Una funzione può anche creare una lista e restituirla con return. La funzione get_values chiede dei numeri all'utente, li mette in una lista e restituisce la lista.

def main():
    numbers = get_values()
    print('I numeri nella lista sono:')
    print(numbers)

def get_values():
    values = []
    again = 's'
    while again == 's':
        num = int(input('Inserisci un numero: '))
        values.append(num)
        print('Vuoi aggiungere un altro numero?')
        again = input('s = sì, qualsiasi altra cosa = no: ')
        print()
    return values

main()

Esempio completo: media senza il voto più basso

Una docente di chimica, prima di calcolare la media di ogni studente, toglie il voto più basso. L'algoritmo:

  1. ottieni i voti dello studente;
  2. calcola il totale dei voti;
  3. trova il voto più basso;
  4. sottrai il voto più basso dal totale: ottieni il totale aggiustato;
  5. dividi il totale aggiustato per il numero di voti meno 1: è la media;
  6. mostra la media.
def main():
    scores = get_scores()
    total = get_total(scores)
    lowest = min(scores)
    total -= lowest
    average = total / (len(scores) - 1)
    print(f'La media, eliminando il voto più basso, è {average:.1f}')

# Chiede i voti all'utente e restituisce la lista dei voti
def get_scores():
    test_scores = []
    again = 's'
    while again == 's':
        value = float(input('Inserisci un voto: '))
        test_scores.append(value)
        print('Vuoi aggiungere un altro voto?')
        again = input('s = sì, qualsiasi altra cosa = no: ')
        print()
    return test_scores

# Restituisce il totale degli elementi della lista
def get_total(value_list):
    total = 0.0
    for num in value_list:
        total += num
    return total

main()

Inserisci almeno due voti: con un voto solo, len(scores) - 1 vale 0 e la divisione dà ZeroDivisionError. Prova con 92, 67, 75, 88: il voto più basso (67) viene scartato e la media è 85.0.

Scegliere elementi a caso

Il modulo random ha tre funzioni utili con le liste:

Funzione Che cosa restituisce
random.choice(lista) un elemento scelto a caso
random.choices(lista, k=n) una lista di n elementi scelti a caso; possono ripetersi
random.sample(lista, k=n) una lista di n elementi scelti a caso, tutti diversi
import random

names = ['Luca', 'Sara', 'Marco', 'Giulia']
winner = random.choice(names)
print('Vincitore:', winner)

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(random.choices(numbers, k=3))   # ad esempio [7, 2, 7]
print(random.sample(numbers, k=3))    # ad esempio [4, 10, 1]

Esegui più volte: il risultato cambia ogni volta.

Liste e file

Spesso vuoi salvare una lista in un file e rileggerla più tardi. Ad esempio, per ordinare dei valori scritti in un file puoi leggerli in una lista, chiamare sort e riscriverli nel file.

Scrivere una lista di stringhe. Il metodo writelines scrive su file tutti gli elementi di una lista, ma non aggiunge '\n': gli elementi finiscono tutti attaccati sulla stessa riga. Di solito conviene un ciclo che scrive ogni elemento seguito da '\n'.

Leggere un file in una lista. Il metodo readlines restituisce il contenuto del file come una lista di stringhe: ogni riga del file è un elemento. Ogni elemento termina con '\n', che di solito si toglie con rstrip('\n').

cities = ['Napoli', 'Roma', 'Milano', 'Torino']

# writelines: nessun a capo tra gli elementi
outfile = open('cities.txt', 'w')
outfile.writelines(cities)
outfile.close()
infile = open('cities.txt', 'r')
print(infile.read())        # NapoliRomaMilanoTorino
infile.close()

# Un elemento per riga
outfile = open('cities.txt', 'w')
for item in cities:
    outfile.write(item + '\n')
outfile.close()

# Rilettura con readlines
infile = open('cities.txt', 'r')
cities_read = infile.readlines()
infile.close()
print(cities_read)          # ogni elemento finisce con '\n'

for index in range(len(cities_read)):
    cities_read[index] = cities_read[index].rstrip('\n')
print(cities_read)          # ['Napoli', 'Roma', 'Milano', 'Torino']

Liste di numeri. Il metodo write accetta solo stringhe: prima di scrivere un numero lo converti con str e aggiungi '\n'. Quando rileggi, ogni riga è una stringa e la riconverti con int (o float). Questo esempio salva dei numeri in disordine, li rilegge, li ordina e li riscrive.

numbers = [42, 7, 19, 3, 25]

# Scrive i numeri, uno per riga
outfile = open('numberlist.txt', 'w')
for item in numbers:
    outfile.write(str(item) + '\n')
outfile.close()

# Legge le righe e le converte in interi
infile = open('numberlist.txt', 'r')
numbers_read = infile.readlines()
infile.close()
for index in range(len(numbers_read)):
    numbers_read[index] = int(numbers_read[index])   # int ignora il '\n' finale

# Ordina e riscrive il file
numbers_read.sort()
outfile = open('numberlist.txt', 'w')
for item in numbers_read:
    outfile.write(str(item) + '\n')
outfile.close()

print('Valori ordinati:', numbers_read)

Righe con più campi: il metodo split

Se una riga contiene più dati separati da un carattere, come Dune;8.50;12, il metodo delle stringhe split(';') la divide in una lista: 'Dune;8.50;12'.split(';') restituisce ['Dune', '8.50', '12']. I numeri restano stringhe: convertili con float e int.

List comprehension

Una list comprehension (comprensione di lista) è un'espressione compatta che crea una nuova lista partendo dagli elementi di un'altra. Questi due pezzi di codice fanno la stessa cosa:

# Con il ciclo for
list1 = [1, 2, 3, 4]
list2 = []
for item in list1:
    list2.append(item)

# Con la list comprehension
list1 = [1, 2, 3, 4]
list2 = [item for item in list1]

Il formato generale è:

[espressione_risultato espressione_iterazione]
  • l'espressione di iterazione (for item in list1) scorre la lista di partenza;
  • l'espressione risultato (item) dice che cosa mettere nella nuova lista a ogni giro.

L'espressione risultato può fare un calcolo. Ad esempio, i quadrati dei numeri di una lista, o la lunghezza di ogni stringa:

list1 = [1, 2, 3, 4]
list2 = [item**2 for item in list1]
print(list2)            # [1, 4, 9, 16]

str_list = ['nave', 'motore', 'elica']
len_list = [len(s) for s in str_list]
print(len_list)         # [4, 6, 5]

La clausola if

Puoi aggiungere una clausola if alla fine: nella nuova lista entrano solo gli elementi che soddisfano la condizione.

[espressione_risultato espressione_iterazione clausola_if]
list1 = [1, 12, 2, 20, 3, 15, 4]

# Con il ciclo for
list2 = []
for n in list1:
    if n < 10:
        list2.append(n)
print(list2)            # [1, 2, 3, 4]

# Con la list comprehension
list2 = [item for item in list1 if item < 10]
print(list2)            # [1, 2, 3, 4]

last_names = ['Rossi', 'Esposito', 'Bianchi', 'Neri']
short_names = [name for name in last_names if len(name) < 6]
print(short_names)      # ['Rossi', 'Neri']

Liste bidimensionali

Una lista bidimensionale (o lista di liste, o lista annidata) è una lista i cui elementi sono a loro volta liste.

students = [['Luca', 'Sara'], ['Marco', 'Giulia'], ['Paola', 'Andrea']]
print(students)
print(students[0])      # ['Luca', 'Sara']
print(students[1])      # ['Marco', 'Giulia']
print(students[2])      # ['Paola', 'Andrea']

Conviene immaginarla come una tabella con righe e colonne. Per un singolo elemento servono due indici: il primo indica la riga, il secondo la colonna.

colonna 0 colonna 1 colonna 2
riga 0 scores[0][0] scores[0][1] scores[0][2]
riga 1 scores[1][0] scores[1][1] scores[1][2]
riga 2 scores[2][0] scores[2][1] scores[2][2]

Esempio: i voti di tre studenti. Un insegnante ha tre studenti, e ognuno sostiene tre esami. Potresti usare tre liste separate, ma dovresti elaborarle una per una. È meglio una lista bidimensionale con tre righe (una per studente) e tre colonne (una per esame). Per elaborarla usi due cicli annidati: quello esterno scorre le righe, quello interno le colonne.

ROWS = 3        # studenti
COLS = 3        # esami
scores = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

# Riempie la tabella
scores[0] = [28, 25, 30]
scores[1] = [18, 22, 20]
scores[2][0] = 30
scores[2][1] = 27
scores[2][2] = 24

# Media di ogni studente (ogni riga)
for r in range(ROWS):
    total = 0
    for c in range(COLS):
        total += scores[r][c]
    print(f'Studente {r + 1}: media {total / COLS:.2f}')

Questo ciclo stampa ogni elemento con i suoi due indici. Nota che len(numbers) è il numero di righe e len(numbers[i]) il numero di colonne della riga i.

numbers = [[1, 2], [10, 20], [100, 200], [1000, 2000]]   # 4 righe, 2 colonne
for i in range(len(numbers)):
    for j in range(len(numbers[i])):
        print(f'numbers[{i}][{j}] = {numbers[i][j]}')

Per creare una tabella di 3 righe e 4 colonne piena di zeri puoi usare una list comprehension annidata. Il nome _ si usa per una variabile che non serve.

matrix = [[0 for _ in range(4)] for _ in range(3)]
print(matrix)           # [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

Non usare [[0] * 4] * 3

Sembra uguale, ma crea tre riferimenti alla stessa riga (come list2 = list1). Se modifichi matrix[0][0], cambia la prima colonna di tutte le righe. Usa la list comprehension dell'esempio, oppure scrivi le righe una per una.

Tuple

Una tupla è una sequenza immutabile: dopo averla creata non puoi modificarla. Si scrive tra parentesi tonde.

my_tuple = (1, 2, 3, 4, 5)
print(my_tuple)

names = ('Luca', 'Sara', 'Marco')
for n in names:                 # ciclo sugli elementi
    print(n)
for i in range(len(names)):     # ciclo sugli indici
    print(i, names[i])

Le tuple supportano tutte le operazioni delle liste che non le modificano:

  • indici e slicing;
  • metodi come index;
  • funzioni integrate come len, min e max;
  • l'operatore in;
  • gli operatori + e *.

Non hanno invece i metodi che cambierebbero il contenuto: append, remove, insert, reverse, sort. Anche assegnare un elemento dà errore. L'esempio che segue mostra apposta questo errore.

names = ('Luca', 'Sara', 'Marco')
print(names[1])
print('Sara' in names)
names[0] = 'Paola'      # TypeError: 'tuple' object does not support item assignment

Tupla con un solo elemento

Per creare una tupla con un solo elemento serve una virgola dopo il valore: my_tuple = (1,). Senza virgola, value = (1) è semplicemente il numero intero 1 tra parentesi.

A che cosa servono, se sono come le liste ma non si possono cambiare?

  • prestazioni: Python elabora una tupla più velocemente di una lista; sono ideali per grandi quantità di dati da non modificare;
  • sicurezza: nessuna parte del programma può cambiare per errore il contenuto.

Conversioni. La funzione list() converte una tupla in lista; la funzione tuple() converte una lista in tupla.

number_tuple = (1, 2, 3)
number_list = list(number_tuple)
print(number_list)      # [1, 2, 3]

str_list = ['uno', 'due', 'tre']
str_tuple = tuple(str_list)
print(str_tuple)        # ('uno', 'due', 'tre')

Grafici con matplotlib

Esiste anche matplotlib, una libreria per disegnare grafici a partire dai dati di una lista. Non fa parte di Python standard: si installa a parte con pip install matplotlib (Windows) o pip3 install matplotlib (Mac e Linux). Per verificare l'installazione scrivi import matplotlib: se non compare nessun errore, è tutto a posto.

Set

Un set (insieme) è una collezione di elementi unici e non ordinati:

  • unici: lo stesso valore non può comparire due volte;
  • non ordinati: gli elementi non hanno una posizione, quindi non puoi usare gli indici (s[0] dà errore).

Si scrive tra parentesi graffe: s = {val1, val2, val3}. Un set vuoto si crea con set() (le graffe vuote {} creano un dizionario). Le funzioni set() e list() convertono una lista in set e viceversa: è il modo più semplice per togliere i duplicati da una lista.

voti = [28, 30, 28, 18, 30, 25]
voti_diversi = set(voti)
print(voti_diversi)             # i duplicati spariscono
print(len(voti_diversi))        # 4
senza_doppioni = list(voti_diversi)
print(senza_doppioni)

L'ordine in cui Python stampa un set non è garantito: non contarci.

Operazione Che cosa fa
s.add(val) aggiunge val al set
s.remove(val) elimina val; se non c'è, solleva KeyError
s.discard(val) elimina val se c'è; se non c'è, non dà errore
s1 | s2 unione: gli elementi che stanno in almeno uno dei due set
s1 & s2 intersezione: gli elementi che stanno in entrambi
s1 - s2 differenza: gli elementi di s1 che non stanno in s2
s1 = {1, 2, 3, 4}
s2 = {3, 4, 5}
s1.add(10)
s1.discard(99)          # 99 non c'è: nessun errore
print(3 in s1)          # True: anche i set supportano in
print(s1 | s2)          # unione
print(s1 & s2)          # intersezione: {3, 4}
print(s1 - s2)          # differenza

Dizionari

Un dizionario contiene coppie chiave: valore. Ogni valore è identificato da una chiave unica, come in un vocabolario la parola identifica la sua definizione. Si scrive tra parentesi graffe:

d = {chiave1: valore1, chiave2: valore2, chiave3: valore3}
  • per leggere un valore scrivi la chiave tra quadre: d[chiave]. Se la chiave non esiste, Python solleva un KeyError;
  • il dizionario è mutabile: puoi aggiungere, modificare ed eliminare coppie; le chiavi però devono essere valori immutabili (stringhe, numeri, tuple);
  • l'operatore in dice se una chiave è presente;
  • un dizionario si può creare anche da una lista di tuple: d = dict([(k1, v1), (k2, v2)]).
phonebook = {'Luca': '555-1111', 'Sara': '555-2222', 'Marco': '555-3333'}
print(phonebook['Sara'])            # 555-2222

phonebook['Giulia'] = '555-4444'    # chiave nuova: aggiunge la coppia
phonebook['Luca'] = '555-0000'      # chiave esistente: cambia il valore
del phonebook['Marco']              # elimina la coppia
print(phonebook)
print(len(phonebook))               # 3 coppie

if 'Marco' in phonebook:
    print(phonebook['Marco'])
else:
    print('Marco non è in rubrica')

prezzi = dict([('pane', 1.20), ('latte', 0.95)])
print(prezzi)

Metodi dei dizionari

Metodo Che cosa fa
d.get(key) restituisce il valore di key; se la chiave non c'è, restituisce None (nessun errore)
d.get(key, val) come sopra, ma se la chiave non c'è restituisce val
d.keys() le chiavi del dizionario
d.values() i valori del dizionario
d.items() le coppie (chiave, valore), come tuple
d.update(d2) aggiunge a d le coppie di d2 (se una chiave c'è già, ne cambia il valore)
d.copy() restituisce una copia del dizionario
d.clear() elimina tutti gli elementi

keys, values e items si usano di solito in un ciclo for; se ti serve una vera lista scrivi ad esempio list(d.keys()). Per sapere se una chiave esiste, in Python 3 si usa in (il vecchio metodo has_key non esiste più).

magazzino = {'viti': 120, 'bulloni': 80}
print(magazzino.get('dadi'))        # None: la chiave non c'è
print(magazzino.get('dadi', 0))     # 0: valore di default

magazzino.update({'dadi': 50, 'viti': 100})
print(magazzino)

for chiave in magazzino:            # un for su un dizionario scorre le chiavi
    print(chiave, '->', magazzino[chiave])

for chiave, valore in magazzino.items():   # ogni coppia è una tupla di 2 elementi
    print(f'{chiave}: {valore} pezzi')

print(list(magazzino.keys()))
print(list(magazzino.values()))

Nel secondo for la tupla (chiave, valore) viene "spacchettata" in due variabili, una per elemento.

Contare e accumulare con un dizionario

Un uso molto frequente: accumulare un totale per ogni chiave. Se la chiave c'è già aggiorni il valore, altrimenti la crei.

vendite = [('pane', 2), ('latte', 1), ('pane', 3), ('uova', 6), ('latte', 2)]
totali = {}
for prodotto, quantita in vendite:
    if prodotto in totali:
        totali[prodotto] += quantita
    else:
        totali[prodotto] = quantita
print(totali)           # {'pane': 5, 'latte': 3, 'uova': 6}

Riepilogo: quale struttura usare?

  • Liste e tuple servono per sequenze ordinate di elementi. La tupla è "protetta" perché immutabile.
  • I set servono per insiemi di dati senza duplicati e senza ordine.
  • I dizionari servono per associare ogni dato a una chiave unica.
Caratteristica Lista Tupla Set Dizionario
Mutabile sì no sì sì
Accesso con indice sì sì no sì, con la chiave
Duplicati sì sì no chiavi no, valori sì
Ordinata sì sì no sì (da Python 3.7)
Sintassi [x, y] (x, y) {x, y} o set() {x: x1, y: y1}

Tre modi per rappresentare una tabella di dati

Nelle prove d'esame devi spesso gestire un elenco di "oggetti" con più informazioni ciascuno: i film di un cinema, i lavaggi di una lavanderia, le camere di un albergo. Prendiamo tre film, ognuno con titolo, prezzo del biglietto e posti disponibili:

Titolo Prezzo Posti
Dune 8.50 12
Inside Out 7.00 5
Interstellar 6.50 20

Ci sono tre modi comuni per rappresentare questa tabella in Python.

1. Liste parallele

Usi una lista per ogni colonna. Le liste sono parallele: lo stesso indice indica lo stesso film in tutte le liste. titoli[1], prezzi[1] e posti[1] sono tutti dati di Inside Out.

titoli = ['Dune', 'Inside Out', 'Interstellar']
prezzi = [8.50, 7.00, 6.50]
posti = [12, 5, 20]

for i in range(len(titoli)):
    print(f'{i + 1} - {titoli[i]} - {prezzi[i]:.2f} euro - posti: {posti[i]}')

# Vendo 3 biglietti per il film numero 1 (indice 0)
posti[0] -= 3
print('Posti rimasti per', titoli[0], ':', posti[0])

È la soluzione più semplice. Attenzione però: se aggiungi o elimini un film, devi farlo in tutte le liste, altrimenti i dati non corrispondono più.

2. Lista di liste

Usi una lista per ogni riga (un film) e metti tutte le righe in una lista. È una lista bidimensionale: film[i] è il film i, e film[i][0], film[i][1], film[i][2] sono titolo, prezzo e posti.

film = [['Dune', 8.50, 12],
        ['Inside Out', 7.00, 5],
        ['Interstellar', 6.50, 20]]

for i in range(len(film)):
    print(f'{i + 1} - {film[i][0]} - {film[i][1]:.2f} euro - posti: {film[i][2]}')

film[0][2] -= 3         # riga 0 (Dune), colonna 2 (posti)
print('Posti rimasti per', film[0][0], ':', film[0][2])

I dati di un film restano insieme. Devi però ricordare che cosa c'è in ogni colonna (0 = titolo, 1 = prezzo, 2 = posti).

3. Lista di dizionari

Ogni film è un dizionario con chiavi dai nomi chiari; tutti i dizionari stanno in una lista.

film = [{'titolo': 'Dune', 'prezzo': 8.50, 'posti': 12},
        {'titolo': 'Inside Out', 'prezzo': 7.00, 'posti': 5},
        {'titolo': 'Interstellar', 'prezzo': 6.50, 'posti': 20}]

for i in range(len(film)):
    f = film[i]
    print(f"{i + 1} - {f['titolo']} - {f['prezzo']:.2f} euro - posti: {f['posti']}")

film[0]['posti'] -= 3
print('Posti rimasti per', film[0]['titolo'], ':', film[0]['posti'])

Il codice è più lungo da scrivere, ma si legge meglio: film[0]['posti'] dice da solo che cosa contiene. Nota le virgolette doppie della f-string: dentro le graffe usiamo gli apici singoli per le chiavi.

Rappresentazione Un film è... Prezzo del film i
liste parallele lo stesso indice in più liste prezzi[i]
lista di liste una lista film[i][1]
lista di dizionari un dizionario film[i]['prezzo']

Dal file alle liste

Nelle tracce d'esame i dati arrivano da un file. Qui ogni film occupa tre righe consecutive: titolo, prezzo, posti. Il programma crea il file (all'esame è già pronto), lo legge con readlines e scorre le righe a gruppi di tre con range(0, len(righe), 3).

# Crea il file di prova
outfile = open('film.txt', 'w')
outfile.write('Dune\n8.50\n12\nInside Out\n7.00\n5\nInterstellar\n6.50\n20\n')
outfile.close()

# Legge il file e riempie tre liste parallele
titoli = []
prezzi = []
posti = []
infile = open('film.txt', 'r')
righe = infile.readlines()
infile.close()
for i in range(0, len(righe), 3):
    titoli.append(righe[i].rstrip('\n'))
    prezzi.append(float(righe[i + 1]))
    posti.append(int(righe[i + 2]))

print(titoli)
print(prezzi)
print(posti)

Per costruire una lista di dizionari, nello stesso ciclo scriveresti film.append({'titolo': ..., 'prezzo': ..., 'posti': ...}).

Errori tipici

Errore Cosa succede Come si corregge
lista[len(lista)] IndexError: l'ultimo indice è len(lista) - 1 usa lista[len(lista) - 1] oppure lista[-1]
nomi = [] e poi nomi[0] = 'Sara' IndexError: la posizione 0 non esiste ancora usa nomi.append('Sara')
copia = lista per fare una copia nessun errore, ma modificando una cambia anche l'altra copia = [] + lista oppure copia = lista[:]
lista = lista.sort() lista diventa None: sort modifica la lista e non restituisce nulla scrivi solo lista.sort()
lista.remove(x) con x assente ValueError controlla prima con if x in lista:
lista + 5 TypeError: si concatenano solo liste lista + [5] oppure lista.append(5)
t = (1) per una tupla di un elemento t è un intero metti la virgola: t = (1,)
d['chiave'] con chiave assente KeyError controlla con in oppure usa d.get('chiave', default)
set_vuoto = {} crei un dizionario vuoto, non un set set_vuoto = set()
liste parallele aggiornate solo in parte nessun errore, ma i dati non corrispondono più aggiungi/elimina nella stessa posizione in tutte le liste

Esercizi

Esercizio 1 · Temperature della settimana. Memorizza in una lista le temperature massime di una settimana: [18.5, 21.0, 19.5, 23.0, 22.5, 17.0, 20.5]. Mostra la temperatura più alta, la più bassa e la media (con un decimale). Poi mostra quanti giorni hanno avuto una temperatura sopra la media.

# Scrivi qui la tua soluzione
Soluzione
temperature = [18.5, 21.0, 19.5, 23.0, 22.5, 17.0, 20.5]

totale = 0.0
for t in temperature:
    totale += t
media = totale / len(temperature)

sopra = 0
for t in temperature:
    if t > media:
        sopra += 1

print('Massima:', max(temperature))
print('Minima:', min(temperature))
print(f'Media: {media:.1f}')
print('Giorni sopra la media:', sopra)

La media è 20.3 e i giorni sopra la media sono 4 (21.0, 23.0, 22.5 e 20.5). Con una list comprehension il conteggio diventa len([t for t in temperature if t > media]).

Esercizio 2 · Biglietteria con lista di dizionari. Parti dalle liste parallele titoli = ['Dune', 'Inside Out', 'Interstellar'], prezzi = [8.50, 7.00, 6.50], posti = [12, 5, 20]. Costruisci una lista di dizionari con le chiavi titolo, prezzo e posti. Mostra il menu numerato dei film (da 1). Chiedi il numero del film e il numero di biglietti. Se il film non esiste scrivi Film non valido; se i posti non bastano scrivi Posti insufficienti; altrimenti mostra il totale da pagare e aggiorna i posti del film.

# Scrivi qui la tua soluzione
Soluzione
titoli = ['Dune', 'Inside Out', 'Interstellar']
prezzi = [8.50, 7.00, 6.50]
posti = [12, 5, 20]

# Dalle liste parallele alla lista di dizionari
film = []
for i in range(len(titoli)):
    film.append({'titolo': titoli[i], 'prezzo': prezzi[i], 'posti': posti[i]})

for i in range(len(film)):
    print(f"{i + 1} - {film[i]['titolo']} - {film[i]['prezzo']:.2f} euro - posti: {film[i]['posti']}")

scelta = int(input('Scegli un film: '))
if scelta < 1 or scelta > len(film):
    print('Film non valido')
else:
    f = film[scelta - 1]          # il menu parte da 1, gli indici da 0
    biglietti = int(input('Quanti biglietti? '))
    if biglietti > f['posti']:
        print('Posti insufficienti')
    else:
        totale = biglietti * f['prezzo']
        f['posti'] -= biglietti
        print(f'Totale da pagare: {totale:.2f} euro')
        print(f"Posti rimanenti per {f['titolo']}: {f['posti']}")

Prova con film 1 e 3 biglietti (totale 25.50 euro, restano 9 posti), con film 2 e 6 biglietti (posti insufficienti) e con film 7 (non valido). Nota che f = film[scelta - 1] non copia il dizionario: f e film[scelta - 1] sono lo stesso oggetto, quindi f['posti'] -= biglietti aggiorna davvero la lista.

Esercizio 3 · Report degli ordini. Un negozio salva gli ordini del giorno nel file ordini.txt. Ogni riga ha il formato nome_prodotto;quantita_venduta;prezzo_unitario. Lo stesso prodotto può comparire in più righe. Scrivi un programma che legga il file, calcoli il guadagno totale per ogni prodotto e stampi un report. Dividi il programma in funzioni e scrivi per ognuna un commento che descrive che cosa fa, i parametri (con tipo) e il valore restituito (con tipo). Il codice di partenza crea il file di prova.

# Crea il file di prova (all'esame il file è già pronto)
outfile = open('ordini.txt', 'w')
outfile.write('penna;10;1.50\n')
outfile.write('quaderno;3;2.80\n')
outfile.write('penna;5;1.50\n')
outfile.write('zaino;1;35.00\n')
outfile.write('quaderno;2;2.80\n')
outfile.close()

# Scrivi qui la tua soluzione
Soluzione
# Crea il file di prova (all'esame il file è già pronto)
outfile = open('ordini.txt', 'w')
outfile.write('penna;10;1.50\n')
outfile.write('quaderno;3;2.80\n')
outfile.write('penna;5;1.50\n')
outfile.write('zaino;1;35.00\n')
outfile.write('quaderno;2;2.80\n')
outfile.close()


def leggi_ordini(nome_file):
    """Legge gli ordini dal file.
    Parametri: nome_file (str), il nome del file da leggere.
    Restituisce: list, una lista di dizionari con chiavi
    'prodotto' (str), 'quantita' (int) e 'prezzo' (float)."""
    ordini = []
    infile = open(nome_file, 'r')
    for riga in infile:
        campi = riga.rstrip('\n').split(';')
        ordini.append({'prodotto': campi[0],
                       'quantita': int(campi[1]),
                       'prezzo': float(campi[2])})
    infile.close()
    return ordini


def calcola_guadagni(ordini):
    """Calcola il guadagno totale di ogni prodotto.
    Parametri: ordini (list), la lista di dizionari degli ordini.
    Restituisce: dict, con chiave il nome del prodotto (str)
    e valore il guadagno totale (float)."""
    guadagni = {}
    for ordine in ordini:
        importo = ordine['quantita'] * ordine['prezzo']
        if ordine['prodotto'] in guadagni:
            guadagni[ordine['prodotto']] += importo
        else:
            guadagni[ordine['prodotto']] = importo
    return guadagni


def stampa_report(guadagni):
    """Stampa il guadagno di ogni prodotto e il totale.
    Parametri: guadagni (dict), prodotto (str) -> guadagno (float).
    Restituisce: None."""
    print('--- Report vendite ---')
    totale = 0.0
    for prodotto, guadagno in guadagni.items():
        print(f'{prodotto:<10} {guadagno:8.2f} euro')
        totale += guadagno
    print(f'{"TOTALE":<10} {totale:8.2f} euro')


def main():
    ordini = leggi_ordini('ordini.txt')
    guadagni = calcola_guadagni(ordini)
    stampa_report(guadagni)


main()

Il report mostra penna 22.50, quaderno 14.00, zaino 35.00 e totale 71.50 euro. Il testo tra tre virgolette all'inizio di una funzione si chiama docstring: è il modo standard di documentare una funzione in Python.

Verifica