14. Liste: collezioni ordinate di elementi
Finora in questo libro abbiamo lavorato con singoli pezzi di dati: numeri singoli, stringhe e valori booleani. Ma i programmi reali spesso devono lavorare con collezioni di elementi correlati—una lista di nomi di studenti, una serie di rilevazioni di temperatura, una collezione di prezzi di prodotti o una sequenza di comandi dell’utente. La lista(list) di Python è lo strumento fondamentale per memorizzare e lavorare con collezioni ordinate di dati.
Una lista è una sequenza(sequence) che può contenere più elementi in un ordine specifico. A differenza delle stringhe (che possono contenere solo caratteri), le liste possono contenere qualsiasi tipo di dato: numeri, stringhe, booleani o persino altre liste. Le liste sono anche mutabili(mutable), il che significa che puoi modificarne il contenuto dopo la creazione—aggiungendo elementi, rimuovendo elementi o modificando quelli esistenti.
In questo capitolo esploreremo come creare liste, accedere ai loro elementi, modificarle e usarle per risolvere problemi pratici di programmazione. Alla fine, capirai perché le liste sono una delle strutture dati più potenti e usate con maggiore frequenza in Python.
14.1) Creare liste e accedere agli elementi
14.1.1) Creare liste con parentesi quadre
Il modo più comune per creare una lista è racchiudere gli elementi tra parentesi quadre [], con gli elementi separati da virgole. Ecco un esempio semplice:
# Una lista di nomi di studenti
students = ["Alice", "Bob", "Charlie", "Diana"]
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']Nota come Python visualizza la lista: mostra le parentesi quadre e mette le virgolette attorno a ogni stringa. Questa è la rappresentazione(representation) della lista—il modo in cui Python ti mostra cosa c’è dentro.
Le liste possono contenere qualsiasi tipo di dato. Ecco una lista di punteggi di un test:
# Una lista di punteggi interi
scores = [85, 92, 78, 95, 88]
print(scores) # Output: [85, 92, 78, 95, 88]Puoi anche mescolare tipi diversi nella stessa lista, anche se nella pratica è meno comune:
# Una lista con tipi misti (meno comune ma valida)
mixed_data = ["Alice", 25, True, 3.14]
print(mixed_data) # Output: ['Alice', 25, True, 3.14]Una lista vuota(empty list) non contiene elementi ed è creata con sole parentesi quadre:
# Una lista vuota
empty = []
print(empty) # Output: []
print(len(empty)) # Output: 0La funzione len(), che abbiamo usato con le stringhe, funziona anche con le liste—restituisce il numero di elementi nella lista.
14.1.2) Comprendere l’ordine e le posizioni nelle liste
Le liste mantengono l’ordine(order) con cui aggiungi gli elementi. Il primo elemento che inserisci resta il primo, il secondo resta il secondo e così via. Questo ordinamento è cruciale perché ti permette di accedere a elementi specifici in base alla loro posizione(position) (chiamata anche indice(index)).
Python usa l’indicizzazione a base zero(zero-based indexing): il primo elemento è in posizione 0, il secondo in posizione 1 e così via. All’inizio potrebbe sembrare insolito, ma è una convenzione usata da molti linguaggi di programmazione.
Vediamo come funziona nella pratica:
students = ["Alice", "Bob", "Charlie", "Diana"]
# Accedi al primo studente (indice 0)
first_student = students[0]
print(first_student) # Output: Alice
# Accedi al terzo studente (indice 2)
third_student = students[2]
print(third_student) # Output: CharlieNota che per ottenere il terzo studente usiamo l’indice 2, non 3. Questo perché il conteggio parte da 0.
14.1.3) Accedere agli elementi con indici positivi
Per accedere a un elemento di una lista, scrivi il nome della lista seguito dall’indice tra parentesi quadre: list_name[index]. L’indice deve essere un intero nell’intervallo valido (da 0 a len(list) - 1).
Ecco un esempio pratico con prezzi di prodotti:
# Prezzi dei prodotti in dollari
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
# Accedi a prezzi specifici
first_price = prices[0]
last_index = len(prices) - 1 # Calcola l’ultimo indice valido
last_price = prices[last_index]
print(f"First product costs: ${first_price}") # Output: First product costs: $19.99
print(f"Last product costs: ${last_price}") # Output: Last product costs: $8.99Perché usiamo len(prices) - 1 per l’ultimo indice? Perché se una lista ha 5 elementi, gli indici sono 0, 1, 2, 3, 4—l’ultimo indice valido è sempre uno in meno della lunghezza.
Puoi anche usare gli indici in espressioni e calcoli:
scores = [85, 92, 78, 95, 88]
# Calcola la media dei primi tre punteggi
first_three_average = (scores[0] + scores[1] + scores[2]) / 3
print(f"Average of first three: {first_three_average}") # Output: Average of first three: 85.014.1.4) Indici negativi: contare dalla fine
Python offre una funzionalità comoda: gli indici negativi(negative indices) ti permettono di accedere agli elementi dalla fine della lista. L’indice -1 si riferisce all’ultimo elemento, -2 al penultimo e così via.
students = ["Alice", "Bob", "Charlie", "Diana"]
# Accedi dalla fine
last_student = students[-1]
second_to_last = students[-2]
print(last_student) # Output: Diana
print(second_to_last) # Output: CharlieQuesto è particolarmente utile quando vuoi l’ultimo elemento ma non vuoi calcolare len(list) - 1:
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
# Questi due approcci sono equivalenti
last_price_method1 = prices[len(prices) - 1]
last_price_method2 = prices[-1]
print(last_price_method1) # Output: 8.99
print(last_price_method2) # Output: 8.99Ecco come gli indici positivi e negativi si mappano sugli stessi elementi:
14.1.5) Cosa succede con indici non validi
Se provi ad accedere a un indice che non esiste, Python solleva un IndexError:
students = ["Alice", "Bob", "Charlie"]
# ATTENZIONE: questa lista ha indici 0, 1, 2 (oppure -3, -2, -1) - solo a scopo dimostrativo
# Provare ad accedere all’indice 3 causa un errore
# PROBLEMA: l’indice 3 non esiste in una lista di 3 elementi
# print(students[3]) # IndexError: list index out of rangeQuesto errore è il modo in cui Python ti dice che hai richiesto un elemento che non c’è.
14.2) Indicizzazione e slicing delle liste
14.2.1) Comprendere le basi dello slicing delle liste
Così come possiamo fare slicing delle stringhe (come abbiamo imparato nel Capitolo 5), possiamo fare slicing(slice) delle liste per estrarne porzioni. Uno slice crea una nuova lista che contiene un sottoinsieme degli elementi della lista originale. La sintassi è list[start:stop], dove start è l’indice in cui inizia lo slice (inclusivo) e stop è dove finisce (esclusivo).
numbers = [10, 20, 30, 40, 50, 60, 70]
# Ottieni gli elementi dall’indice 1 fino a (ma non includendo) l’indice 4
subset = numbers[1:4]
print(subset) # Output: [20, 30, 40]Lo slice [1:4] include gli indici 1, 2 e 3, ma si ferma prima dell’indice 4. Questa regola del "fine esclusiva" è la stessa dello slicing delle stringhe.
Vediamo un esempio pratico con nomi di studenti:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
# Ottieni i primi tre studenti
first_three = students[0:3]
print(first_three) # Output: ['Alice', 'Bob', 'Charlie']
# Ottieni gli studenti dall’indice 2 al 4
middle_group = students[2:5]
print(middle_group) # Output: ['Charlie', 'Diana', 'Eve']14.2.2) Omettere start o stop negli slice
Puoi omettere l’indice di inizio per fare slicing dall’inizio, oppure omettere l’indice di fine per fare slicing fino alla fine:
scores = [85, 92, 78, 95, 88, 91, 87]
# Dall’inizio fino all’indice 3
first_few = scores[:3]
print(first_few) # Output: [85, 92, 78]
# Dall’indice 4 fino alla fine
last_few = scores[4:]
print(last_few) # Output: [88, 91, 87]
# L’intera lista (dall’inizio alla fine)
all_scores = scores[:]
print(all_scores) # Output: [85, 92, 78, 95, 88, 91, 87]Lo slice [:] crea una copia(copy) dell’intera lista. Questo è utile quando vuoi lavorare con un duplicato senza modificare l’originale—lo esploreremo meglio nella sezione 14.6.
14.2.3) Usare indici negativi negli slice
Gli indici negativi funzionano negli slice proprio come funzionano nell’accesso a un singolo elemento. Questo è particolarmente utile per ottenere elementi dalla fine:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
# Ottieni gli ultimi tre studenti
last_three = students[-3:]
print(last_three) # Output: ['Diana', 'Eve', 'Frank']
# Ottieni tutti tranne gli ultimi due studenti
all_but_last_two = students[:-2]
print(all_but_last_two) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']
# Ottieni dal terzultimo al penultimo
middle_from_end = students[-3:-1]
print(middle_from_end) # Output: ['Diana', 'Eve']14.2.4) Slicing con un valore di step
Puoi aggiungere un terzo parametro per controllare lo step(step) (quanti indici saltare tra gli elementi). La sintassi completa è list[start:stop:step]:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Ogni secondo numero a partire dall’indice 0
evens = numbers[0:10:2]
print(evens) # Output: [0, 2, 4, 6, 8]
# Ogni terzo numero a partire dall’indice 1
every_third = numbers[1:10:3]
print(every_third) # Output: [1, 4, 7]Puoi anche usare uno step negativo(negative step) per invertire la lista:
numbers = [1, 2, 3, 4, 5]
# Inverti la lista
reversed_numbers = numbers[::-1]
print(reversed_numbers) # Output: [5, 4, 3, 2, 1]Lo slice [::-1] significa "parti dalla fine, vai all’inizio, procedendo all’indietro di 1." Questo è un modo di dire Python comune per invertire le sequenze.
14.2.5) Gli slice non causano mai IndexError
A differenza dell’accesso a un singolo elemento, lo slicing è molto tollerante. Se specifichi indici fuori dall’intervallo della lista, Python li adatta semplicemente:
numbers = [10, 20, 30, 40, 50]
# Chiedere più di quanto esista
extended_slice = numbers[2:100]
print(extended_slice) # Output: [30, 40, 50]
# Iniziare oltre la fine
empty_slice = numbers[10:20]
print(empty_slice) # Output: []Questo comportamento è utile perché significa che non devi preoccuparti di limiti esatti quando fai slicing—Python gestisce i casi limite con eleganza.
14.3) Modificare le liste e metodi comuni delle liste
14.3.1) Le liste sono mutabili: modificare gli elementi
A differenza delle stringhe, che sono immutabili, le liste sono mutabili—puoi modificarne il contenuto dopo la creazione. Puoi modificare elementi individuali assegnando nuovi valori a indici specifici:
# Inizia con una lista di prezzi
prices = [19.99, 24.50, 15.75, 32.00]
print(prices) # Output: [19.99, 24.5, 15.75, 32.0]
# Aggiorna il secondo prezzo (indice 1)
prices[1] = 22.99
print(prices) # Output: [19.99, 22.99, 15.75, 32.0]
# Aggiorna l’ultimo prezzo usando l’indicizzazione negativa
prices[-1] = 29.99
print(prices) # Output: [19.99, 22.99, 15.75, 29.99]Questa mutabilità è potente—significa che puoi aggiornare i dati in place senza creare nuove liste. Tuttavia, significa anche che devi fare attenzione a modifiche involontarie, di cui parleremo nella sezione 14.6.
14.3.2) Aggiungere elementi con append()
Il metodo append() aggiunge un singolo elemento alla fine(end) di una lista. Questa è una delle operazioni sulle liste usate più frequentemente:
# Inizia con un carrello della spesa vuoto
cart = []
print(cart) # Output: []
# Aggiungi elementi uno per uno
cart.append("Milk")
print(cart) # Output: ['Milk']
cart.append("Bread")
print(cart) # Output: ['Milk', 'Bread']
cart.append("Eggs")
print(cart) # Output: ['Milk', 'Bread', 'Eggs']Nota che append() modifica la lista in place—non restituisce una nuova lista. Il metodo restituisce None, quindi non serve assegnarne il risultato:
scores = [85, 92, 78]
result = scores.append(95)
print(scores) # Output: [85, 92, 78, 95]
print(result) # Output: None14.3.3) Inserire elementi in posizioni specifiche con insert()
Mentre append() aggiunge sempre alla fine, insert() ti permette di aggiungere un elemento in qualsiasi posizione. La sintassi è list.insert(index, item):
students = ["Alice", "Charlie", "Diana"]
print(students) # Output: ['Alice', 'Charlie', 'Diana']
# Inserisci "Bob" all’indice 1 (tra Alice e Charlie)
students.insert(1, "Bob")
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']Quando inserisci a un indice, l’elemento attualmente in quella posizione (e tutti gli elementi dopo di esso) si spostano a destra:
numbers = [10, 20, 30, 40]
print(numbers) # Output: [10, 20, 30, 40]
# Inserisci 25 all’indice 2
numbers.insert(2, 25)
print(numbers) # Output: [10, 20, 25, 30, 40]Puoi inserire all’inizio usando l’indice 0:
priorities = ["Medium", "Low"]
priorities.insert(0, "High")
print(priorities) # Output: ['High', 'Medium', 'Low']Se specifichi un indice oltre la lunghezza della lista, insert() aggiunge semplicemente l’elemento alla fine (come append()):
items = [1, 2, 3]
items.insert(100, 4)
print(items) # Output: [1, 2, 3, 4]14.3.4) Rimuovere elementi con remove()
Il metodo remove() rimuove la prima occorrenza(first occurrence) di un valore specifico dalla lista:
fruits = ["apple", "banana", "cherry", "banana", "date"]
print(fruits) # Output: ['apple', 'banana', 'cherry', 'banana', 'date']
# Rimuovi la prima "banana"
fruits.remove("banana")
print(fruits) # Output: ['apple', 'cherry', 'banana', 'date']Nota che è stata rimossa solo la prima "banana"—la seconda rimane. Se provi a rimuovere un valore che non esiste, Python solleva un ValueError:
numbers = [10, 20, 30]
# ATTENZIONE: tentativo di rimuovere un valore inesistente - solo a scopo dimostrativo
# PROBLEMA: 40 non è nella lista
# numbers.remove(40) # ValueError: list.remove(x): x not in listPer evitare questo errore, puoi controllare se l’elemento esiste prima di rimuoverlo:
cart = ["Milk", "Bread", "Eggs"]
item_to_remove = "Butter"
if item_to_remove in cart:
cart.remove(item_to_remove)
print(f"Removed {item_to_remove}")
else:
print(f"{item_to_remove} not in cart")
# Output: Butter not in cart14.3.5) Rimuovere e restituire elementi con pop()
Il metodo pop() rimuove un elemento a un indice specifico e lo restituisce(returns). Se non specifichi un indice, rimuove e restituisce l’ultimo elemento:
scores = [85, 92, 78, 95, 88]
# Rimuovi e ottieni l’ultimo punteggio
last_score = scores.pop()
print(f"Removed: {last_score}") # Output: Removed: 88
print(scores) # Output: [85, 92, 78, 95]
# Rimuovi e ottieni il punteggio all’indice 1
second_score = scores.pop(1)
print(f"Removed: {second_score}") # Output: Removed: 92
print(scores) # Output: [85, 78, 95]Questo è utile quando devi elaborare elementi da una lista uno alla volta:
tasks = ["Write code", "Test code", "Deploy code"]
while len(tasks) > 0:
current_task = tasks.pop(0) # Rimuovi dall’inizio
print(f"Working on: {current_task}")
# Output:
# Working on: Write code
# Working on: Test code
# Working on: Deploy code
print(tasks) # Output: []14.3.6) Estendere le liste con extend()
Il metodo extend() aggiunge tutti gli elementi da un’altra lista (o da qualsiasi iterabile) alla fine della lista corrente:
primary_colors = ["red", "blue", "yellow"]
secondary_colors = ["green", "orange", "purple"]
# Aggiungi tutti i colori secondari ai colori primari
primary_colors.extend(secondary_colors)
print(primary_colors)
# Output: ['red', 'blue', 'yellow', 'green', 'orange', 'purple']Questo è diverso da append(), che aggiungerebbe l’intera lista come un singolo elemento:
colors1 = ["red", "blue"]
colors2 = ["green", "orange"]
# Usare append (aggiunge la lista come un elemento)
colors1.append(colors2)
print(colors1) # Output: ['red', 'blue', ['green', 'orange']]
# Usare extend (aggiunge ogni elemento singolarmente)
colors3 = ["red", "blue"]
colors3.extend(colors2)
print(colors3) # Output: ['red', 'blue', 'green', 'orange']14.3.7) Ordinare le liste con sort() e sorted()
Python offre due modi per ordinare le liste. Il metodo sort() ordina la lista in place (modificando l’originale):
scores = [78, 95, 85, 92, 88]
scores.sort()
print(scores) # Output: [78, 85, 88, 92, 95]Per ordinare in ordine decrescente, usa il parametro reverse:
scores = [78, 95, 85, 92, 88]
scores.sort(reverse=True)
print(scores) # Output: [95, 92, 88, 85, 78]La funzione sorted() (che esploreremo di più nel Capitolo 38) crea una nuova lista ordinata(new sorted list) senza modificare l’originale:
original = [78, 95, 85, 92, 88]
sorted_scores = sorted(original)
print(original) # Output: [78, 95, 85, 92, 88]
print(sorted_scores) # Output: [78, 85, 88, 92, 95]L’ordinamento funziona anche con le stringhe, usando l’ordine alfabetico:
names = ["Charlie", "Alice", "Diana", "Bob"]
names.sort()
print(names) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']14.3.8) Invertire le liste con reverse()
Il metodo reverse() inverte la lista in place:
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(numbers) # Output: [5, 4, 3, 2, 1]Questo è diverso dall’ordinare in ordine inverso—reverse() semplicemente ribalta l’ordine corrente, qualunque esso sia:
mixed = [3, 1, 4, 1, 5]
mixed.reverse()
print(mixed) # Output: [5, 1, 4, 1, 3]Ricorda che puoi anche invertire una lista usando lo slicing: list[::-1]. La differenza è che lo slicing crea una nuova lista, mentre reverse() modifica l’originale.
14.3.9) Trovare elementi con index() e count()
Il metodo index() restituisce la posizione della prima occorrenza di un valore:
students = ["Alice", "Bob", "Charlie", "Diana", "Bob"]
# Trova dove si trova "Charlie"
position = students.index("Charlie")
print(f"Charlie is at index {position}") # Output: Charlie is at index 2
# Trova il primo "Bob"
bob_position = students.index("Bob")
print(f"Bob is at index {bob_position}") # Output: Bob is at index 1Se il valore non esiste, index() solleva un ValueError:
students = ["Alice", "Bob", "Charlie"]
# ATTENZIONE: tentativo di trovare un valore inesistente - solo a scopo dimostrativo
# PROBLEMA: 'Eve' non è nella lista
# position = students.index("Eve") # ValueError: 'Eve' is not in listIl metodo count() restituisce quante volte appare un valore:
numbers = [1, 2, 3, 2, 4, 2, 5]
twos = numbers.count(2)
print(f"The number 2 appears {twos} times") # Output: The number 2 appears 3 times
# count può restituire 0 se l’elemento non esiste
sixes = numbers.count(6)
print(f"The number 6 appears {sixes} times") # Output: The number 6 appears 0 times14.3.10) Cancellare tutti gli elementi con clear()
Il metodo clear() rimuove tutti gli elementi da una lista, lasciandola vuota:
cart = ["Milk", "Bread", "Eggs", "Butter"]
print(cart) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
cart.clear()
print(cart) # Output: []
print(len(cart)) # Output: 0Questo è equivalente ad assegnare una lista vuota, ma clear() è più esplicito riguardo all’intenzione.
14.4) Eliminare elementi delle liste con del
14.4.1) Usare del per rimuovere elementi per indice
L’istruzione del può eliminare elementi di una lista a indici specifici:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve"]
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']
# Elimina l’elemento all’indice 2
del students[2]
print(students) # Output: ['Alice', 'Bob', 'Diana', 'Eve']A differenza di pop(), del non restituisce il valore rimosso—lo elimina soltanto. Questo è utile quando vuoi rimuovere un elemento ma non hai bisogno di usarlo:
scores = [85, 92, 78, 95, 88]
# Rimuovi il punteggio più basso (all’indice 2)
del scores[2]
print(scores) # Output: [85, 92, 95, 88]Puoi anche usare indici negativi con del:
tasks = ["Task 1", "Task 2", "Task 3", "Task 4"]
# Elimina l’ultimo task
del tasks[-1]
print(tasks) # Output: ['Task 1', 'Task 2', 'Task 3']14.4.2) Eliminare slice con del
L’istruzione del può rimuovere interi slice in una sola volta:
numbers = [10, 20, 30, 40, 50, 60, 70]
print(numbers) # Output: [10, 20, 30, 40, 50, 60, 70]
# Elimina gli elementi dall’indice 2 al 4 (indici 2, 3, 4)
del numbers[2:5]
print(numbers) # Output: [10, 20, 60, 70]Questo è particolarmente utile per rimuovere intervalli di elementi:
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Rimuovi i primi tre elementi
del data[:3]
print(data) # Output: [4, 5, 6, 7, 8, 9, 10]
# Rimuovi gli ultimi due elementi
del data[-2:]
print(data) # Output: [4, 5, 6, 7, 8]Puoi persino eliminare un elemento sì e uno no usando slicing con step:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Elimina ogni secondo elemento
del numbers[::2]
print(numbers) # Output: [1, 3, 5, 7, 9]14.4.3) Confrontare del, remove() e pop()
Chiariamo quando usare ciascun metodo di eliminazione:
# Lista di esempio per il confronto
items = ["apple", "banana", "cherry", "date", "elderberry"]
# Usa remove() quando conosci il VALORE da eliminare
items_copy1 = items.copy()
items_copy1.remove("cherry") # Rimuove il primo "cherry"
print(items_copy1) # Output: ['apple', 'banana', 'date', 'elderberry']
# Usa pop() quando conosci l’INDICE e ti serve il valore
items_copy2 = items.copy()
removed_item = items_copy2.pop(2) # Rimuove e restituisce l’elemento all’indice 2
print(f"Removed: {removed_item}") # Output: Removed: cherry
print(items_copy2) # Output: ['apple', 'banana', 'date', 'elderberry']
# Usa del quando conosci l’INDICE ma non ti serve il valore
items_copy3 = items.copy()
del items_copy3[2] # Rimuove semplicemente l’elemento all’indice 2
print(items_copy3) # Output: ['apple', 'banana', 'date', 'elderberry']14.5) Iterare sulle liste con cicli for
14.5.1) Iterazione di base sulle liste
Una delle operazioni più comuni con le liste è elaborare ciascun elemento in sequenza. Il ciclo for(for loop) (che abbiamo imparato nel Capitolo 12) è perfetto per questo:
students = ["Alice", "Bob", "Charlie", "Diana"]
# Elabora ogni studente
for student in students:
print(f"Hello, {student}!")
# Output:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!
# Hello, Diana!La variabile del ciclo (student in questo caso) assume ogni valore della lista, uno alla volta, in ordine. Puoi chiamare questa variabile con qualsiasi nome significativo:
scores = [85, 92, 78, 95, 88]
# Calcola e visualizza il voto di ciascun punteggio
for score in scores:
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
print(f"Score {score} is a {grade}")
# Output:
# Score 85 is a B
# Score 92 is a A
# Score 78 is a C
# Score 95 is a A
# Score 88 is a B14.5.2) Elaborare elementi corrispondenti da più liste
A volte devi lavorare con dati correlati memorizzati in liste separate. Impareremo la funzione zip() in dettaglio nel Capitolo 38, ma ecco una breve anteprima di come può aiutare a elaborare elementi corrispondenti:
# Impareremo zip() nel Capitolo 38, ma per ora ecco un esempio semplice
students = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
# Elabora coppie corrispondenti
for student, score in zip(students, scores):
print(f"{student} scored {score}")
# Output:
# Alice scored 85
# Bob scored 92
# Charlie scored 78La funzione zip() accoppia gli elementi di più liste, cosa utile quando hai dati correlati in liste separate. La esploreremo, insieme ad altri strumenti di iterazione, in profondità nel Capitolo 38.
14.6) Copiare le liste ed evitare riferimenti condivisi
14.6.1) Comprendere i riferimenti delle liste
Quando assegni una lista a una variabile, Python non crea una copia della lista—crea un riferimento(reference) allo stesso oggetto lista in memoria. Questo significa che più variabili possono riferirsi alla stessa lista:
original = [1, 2, 3]
reference = original # Entrambe le variabili puntano alla STESSA lista
# Modificare tramite una variabile influenza l’altra
reference.append(4)
print(original) # Output: [1, 2, 3, 4]
print(reference) # Output: [1, 2, 3, 4]Questo comportamento può sorprendere se ti aspetti che reference sia una copia indipendente. Vediamo perché è importante:
# Scenario: vuoi tenere traccia delle modifiche a un carrello della spesa
cart = ["Milk", "Bread"]
backup = cart # Tentativo di salvare lo stato originale
# Aggiungi altri elementi
cart.append("Eggs")
cart.append("Butter")
# Controlla il "backup"
print(backup) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']Anche il backup è cambiato! Questo perché backup e cart sono due nomi per lo stesso oggetto lista.
14.6.2) Creare copie indipendenti con lo slicing
Per creare una vera copia indipendente, usa lo slicing con [:]:
original = [1, 2, 3]
copy = original[:] # Crea una NUOVA lista con gli stessi contenuti
# Modificare la copia non influenza l’originale
copy.append(4)
print(original) # Output: [1, 2, 3]
print(copy) # Output: [1, 2, 3, 4]Ora sistemiamo l’esempio del carrello della spesa:
cart = ["Milk", "Bread"]
backup = cart[:] # Crea una copia indipendente
# Aggiungi altri elementi a cart
cart.append("Eggs")
cart.append("Butter")
# Il backup resta invariato
print(cart) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
print(backup) # Output: ['Milk', 'Bread']14.6.3) Creare copie con il metodo copy()
Le liste hanno anche un metodo copy() che fa la stessa cosa di [:]:
original = [10, 20, 30]
copy = original.copy()
copy.append(40)
print(original) # Output: [10, 20, 30]
print(copy) # Output: [10, 20, 30, 40]Sia [:] sia copy() creano copie superficiali(shallow copies), di cui parleremo ora.
14.6.4) Il limite della copia superficiale
Sia [:] sia copy() creano copie superficiali(shallow copies). Questo significa che copiano la struttura della lista, ma se la lista contiene altri oggetti mutabili (come altre liste), quegli oggetti interni sono ancora condivisi:
# Una lista che contiene liste
original = [[1, 2], [3, 4], [5, 6]]
copy = original[:]
# Modificare la struttura della lista esterna è indipendente
copy.append([7, 8])
print(original) # Output: [[1, 2], [3, 4], [5, 6]]
print(copy) # Output: [[1, 2], [3, 4], [5, 6], [7, 8]]
# Ma modificare una lista interna influenza entrambe!
copy[0].append(99)
print(original) # Output: [[1, 2, 99], [3, 4], [5, 6]]
print(copy) # Output: [[1, 2, 99], [3, 4], [5, 6], [7, 8]]Perché succede? Perché la copia superficiale crea una nuova lista esterna, ma le liste interne sono ancora riferimenti condivisi:
Per strutture annidate, ti servirebbe una copia profonda(deep copy), che impareremo quando esploreremo il modulo copy nei capitoli successivi. Per ora, tieni presente che le copie superficiali funzionano perfettamente per liste di elementi immutabili (numeri, stringhe, tuple), ma richiedono cautela con strutture mutabili annidate.
14.6.5) Quando i riferimenti condivisi sono utili
A volte vuoi che più variabili si riferiscano alla stessa lista. Questo è utile quando devi modificare una lista da parti diverse del tuo codice:
# Una funzione che modifica una lista in place
def add_bonus_points(scores, bonus):
for i in range(len(scores)):
scores[i] = scores[i] + bonus
# La lista originale viene modificata
student_scores = [85, 92, 78]
add_bonus_points(student_scores, 5)
print(student_scores) # Output: [90, 97, 83]Questo funziona perché la funzione riceve un riferimento alla lista originale, non una copia. Lo esploreremo ulteriormente quando studieremo le funzioni in dettaglio nella Parte V.
14.7) Usare enumerate() mentre si itera sulle liste
14.7.1) La necessità di avere sia indice sia valore
A volte, quando iteri su una lista, ti servono sia l’indice sia il valore. Un approccio è usare range(len(list)):
students = ["Alice", "Bob", "Charlie", "Diana"]
for i in range(len(students)):
print(f"Student {i}: {students[i]}")
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: DianaQuesto funziona, ma non è molto elegante. Devi usare students[i] per accedere a ogni valore, cosa che è meno leggibile rispetto a iterare direttamente sui valori.
14.7.2) Usare enumerate() per un codice più pulito
La funzione enumerate() offre una soluzione migliore. Restituisce sia l’indice sia il valore per ogni elemento:
students = ["Alice", "Bob", "Charlie", "Diana"]
for index, student in enumerate(students):
print(f"Student {index}: {student}")
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: DianaLa sintassi for index, value in enumerate(list) effettua l’unpacking di ogni coppia che enumerate() produce. Questo è molto più leggibile rispetto all’uso di range(len()).
14.7.3) Avviare enumerate() da un numero diverso
Per impostazione predefinita, enumerate() inizia a contare da 0. Puoi specificare un numero iniziale diverso con il parametro start:
students = ["Alice", "Bob", "Charlie", "Diana"]
# Inizia a contare da 1 invece che da 0
for position, student in enumerate(students, start=1):
print(f"Position {position}: {student}")
# Output:
# Position 1: Alice
# Position 2: Bob
# Position 3: Charlie
# Position 4: DianaQuesto è utile quando vuoi mostrare una numerazione più naturale per le persone (a partire da 1) invece dell’indicizzazione più naturale per i programmatori (a partire da 0).
Esempi pratici con enumerate()
Ecco un esempio pratico che visualizza un menu numerato:
menu_items = ["New Game", "Load Game", "Settings", "Quit"]
print("Main Menu:")
for number, item in enumerate(menu_items, start=1):
print(f"{number}. {item}")
# Output:
# Main Menu:
# 1. New Game
# 2. Load Game
# 3. Settings
# 4. Quit14.7.4) Modificare le liste con enumerate()
Puoi usare enumerate() quando devi modificare gli elementi della lista in base alla loro posizione:
# Aggiungi un bonus basato sulla posizione ai punteggi
scores = [85, 92, 78, 95, 88]
for index, score in enumerate(scores):
# Il primo studente prende 5 punti bonus, il secondo ne prende 4, ecc.
bonus = 5 - index
if bonus > 0:
scores[index] = score + bonus
print(scores) # Output: [90, 96, 81, 97, 89]14.8) Schemi comuni con le liste: ricerca, filtraggio e aggregazione dei dati
14.8.1) Cercare elementi nelle liste
Uno dei compiti più comuni è controllare se una lista contiene un elemento specifico. L’operatore in (che abbiamo imparato nel Capitolo 7) rende la cosa semplice:
students = ["Alice", "Bob", "Charlie", "Diana"]
# Controlla se uno studente è nella lista
if "Charlie" in students:
print("Charlie is enrolled") # Output: Charlie is enrolled
if "Eve" not in students:
print("Eve is not enrolled") # Output: Eve is not enrolledPer trovare la posizione di un elemento, usa il metodo index() (trattato nella sezione 14.3.9), ma ricorda di controllare prima se l’elemento esiste:
scores = [85, 92, 78, 95, 88]
target_score = 95
if target_score in scores:
position = scores.index(target_score)
print(f"Score {target_score} found at index {position}")
# Output: Score 95 found at index 3
else:
print(f"Score {target_score} not found")14.8.2) Trovare i valori massimo e minimo
Le funzioni built-in max() e min() di Python funzionano con le liste:
scores = [85, 92, 78, 95, 88, 91, 76]
highest_score = max(scores)
lowest_score = min(scores)
print(f"Highest score: {highest_score}") # Output: Highest score: 95
print(f"Lowest score: {lowest_score}") # Output: Lowest score: 7614.8.3) Calcolare aggregati: somma, media e conteggio
Calcolare totali e medie è un’operazione fondamentale sulle liste:
scores = [85, 92, 78, 95, 88, 91, 76, 89]
# Calcola totale e media
total = sum(scores)
count = len(scores)
average = total / count
print(f"Total: {total}") # Output: Total: 694
print(f"Count: {count}") # Output: Count: 8
print(f"Average: {average:.2f}") # Output: Average: 86.75Ecco un esempio pratico che calcola il totale di un carrello della spesa:
cart_items = ["Milk", "Bread", "Eggs", "Butter", "Cheese"]
prices = [3.99, 2.49, 4.99, 5.49, 6.99]
# Calcola il costo totale
total_cost = sum(prices)
item_count = len(cart_items)
print(f"Items in cart: {item_count}")
print(f"Total cost: ${total_cost:.2f}")
# Output:
# Items in cart: 5
# Total cost: $23.9514.9) Mutabilità delle liste e truthiness nelle condizioni
14.9.1) Comprendere la mutabilità delle liste nella pratica
Abbiamo visto in tutto questo capitolo che le liste sono mutabili—possono essere modificate dopo la creazione. Questa mutabilità è ciò che rende le liste così potenti per memorizzare e manipolare collezioni di dati. Consolidiamo la nostra comprensione con un esempio completo:
# Inizia con una lista di task vuota
tasks = []
print(f"Initial tasks: {tasks}") # Output: Initial tasks: []
# Aggiungi task
tasks.append("Write code")
tasks.append("Test code")
tasks.append("Deploy code")
print(f"After adding: {tasks}")
# Output: After adding: ['Write code', 'Test code', 'Deploy code']
# Inserisci un task urgente all’inizio
tasks.insert(0, "Review requirements")
print(f"After inserting: {tasks}")
# Output: After inserting: ['Review requirements', 'Write code', 'Test code', 'Deploy code']
# Completa e rimuovi il primo task
completed = tasks.pop(0)
print(f"Completed: {completed}") # Output: Completed: Review requirements
print(f"Remaining: {tasks}")
# Output: Remaining: ['Write code', 'Test code', 'Deploy code']
# Modifica un task
tasks[1] = "Test code thoroughly"
print(f"After modifying: {tasks}")
# Output: After modifying: ['Write code', 'Test code thoroughly', 'Deploy code']14.9.2) Mutabilità vs immutabilità: liste vs stringhe
È importante capire la differenza tra liste mutabili e stringhe immutabili. Con le stringhe, le operazioni creano nuove stringhe invece di modificare l’originale:
# Le stringhe sono immutabili
text = "hello"
text.upper() # Crea una nuova stringa, non cambia l’originale
print(text) # Output: hello (unchanged)
# Per "cambiare" una stringa, devi riassegnare
text = text.upper()
print(text) # Output: HELLO
# Le liste sono mutabili
numbers = [1, 2, 3]
numbers.append(4) # Modifica la lista in place
print(numbers) # Output: [1, 2, 3, 4] (changed)Questa differenza influisce su come lavori con questi tipi:
# Le operazioni sulle stringhe richiedono riassegnazione
name = "alice"
name = name.capitalize() # Devi riassegnare per vedere il cambiamento
print(name) # Output: Alice
# Le operazioni sulle liste modificano in place
scores = [85, 92, 78]
scores.append(95) # Nessuna riassegnazione necessaria
print(scores) # Output: [85, 92, 78, 95]14.9.3) Usare le liste in contesti booleani
Le liste hanno truthiness: una lista vuota è considerata False, e qualsiasi lista non vuota è considerata True. Questo è utile nelle istruzioni condizionali:
# La lista vuota è falsy
empty_cart = []
if empty_cart:
print("Cart has items")
else:
print("Cart is empty") # Output: Cart is empty
# La lista non vuota è truthy
cart_with_items = ["Milk", "Bread"]
if cart_with_items:
print("Cart has items") # Output: Cart has itemsQuesto schema è usato comunemente per controllare se una lista ha elementi prima di elaborarli:
students = ["Alice", "Bob", "Charlie"]
if students:
print(f"We have {len(students)} students")
for student in students:
print(f" - {student}")
else:
print("No students enrolled")
# Output:
# We have 3 students
# - Alice
# - Bob
# - Charlie14.9.4) Schema pratico: elaborare fino a svuotare
La truthiness delle liste abilita uno schema utile per elaborare elementi finché una lista non è vuota:
# Elabora i task finché non ne rimane nessuno
tasks = ["Task 1", "Task 2", "Task 3"]
while tasks: # Continua finché la lista non è vuota
current_task = tasks.pop(0)
print(f"Processing: {current_task}")
print("All tasks completed!")
# Output:
# Processing: Task 1
# Processing: Task 2
# Processing: Task 3
# All tasks completed!14.9.5) Controllare liste vuote: esplicito vs implicito
Ci sono due modi per controllare se una lista è vuota:
items = []
# Controllo implicito (Pythonic)
if not items:
print("List is empty") # Output: List is empty
# Controllo esplicito (anch’esso valido)
if len(items) == 0:
print("List is empty") # Output: List is emptyIl controllo implicito (if not items:) è generalmente preferito in Python perché è più conciso e funziona con qualsiasi tipo di collezione. Tuttavia, entrambi gli approcci sono corretti e li vedrai entrambi nel codice reale.
14.9.6) Mutabilità e comportamento delle funzioni
Quando passi una lista a una funzione (che esploreremo in dettaglio nella Parte V), la funzione riceve un riferimento allo stesso oggetto lista. Questo significa che la funzione può modificare la lista originale:
def add_item(shopping_list, item):
shopping_list.append(item)
print(f"Added {item}")
# La lista originale viene modificata
cart = ["Milk", "Bread"]
print(f"Before: {cart}") # Output: Before: ['Milk', 'Bread']
add_item(cart, "Eggs") # Output: Added Eggs
print(f"After: {cart}") # Output: After: ['Milk', 'Bread', 'Eggs']Questo comportamento è diverso dai tipi immutabili come stringhe e numeri, in cui il valore originale non può essere modificato da una funzione. Comprendere questa distinzione è fondamentale per scrivere programmi corretti.
Le liste sono una delle strutture dati più fondamentali e versatili di Python. Forniscono una collezione ordinata e mutabile che può crescere e ridursi in base alle necessità, rendendole perfette per memorizzare ed elaborare sequenze di dati correlati. Hai imparato a creare liste, accedere ai loro elementi tramite indicizzazione e slicing, modificarle con vari metodi, iterare su di esse in modo efficiente e comprenderne la natura mutabile.
Gli schemi che abbiamo esplorato—ricerca, filtraggio, aggregazione e trasformazione dei dati—costituiscono le fondamenta per lavorare con le collezioni in Python. Man mano che continui a imparare, scoprirai modi ancora più potenti per lavorare con le liste, incluse le list comprehension (Capitolo 35) e tecniche avanzate di iterazione (Capitoli 36-37). Ma i fondamenti che hai padroneggiato in questo capitolo ti saranno utili per tutto il tuo percorso di programmazione in Python.