15. Tuple e range: sequenze semplici immutabili
Nel Capitolo 14, abbiamo esplorato le liste(lists), il versatile tipo di sequenza mutabile di Python. Ora esamineremo altri due importanti tipi di sequenza: tuple(tuples) e range(ranges). Mentre le liste eccellono nel memorizzare collezioni che cambiano nel tempo, le tuple forniscono sequenze immutabili che proteggono i dati dalle modifiche, e i range offrono modi efficienti in termini di memoria per rappresentare sequenze di numeri.
Capire quando usare ciascun tipo di sequenza renderà i tuoi programmi più efficienti, più sicuri e più chiari nell’intento. Alla fine di questo capitolo, saprai come lavorare con tuple e range in modo efficace e comprenderai le operazioni comuni che funzionano su tutti i tipi di sequenza Python.
15.1) Creare e usare le tuple (l’importanza della virgola)
Una tupla(tuple) è una sequenza ordinata e immutabile di elementi. Come le liste, le tuple possono contenere qualsiasi tipo di dato e mantenere l’ordine degli elementi. Tuttavia, a differenza delle liste, una volta creata una tupla non puoi modificarne il contenuto.
Creare tuple con le parentesi
Il modo più comune per creare una tupla è racchiudere valori separati da virgole tra parentesi:
# Una tupla di punteggi dei test degli studenti
scores = (85, 92, 78, 95)
print(scores) # Output: (85, 92, 78, 95)
print(type(scores)) # Output: <class 'tuple'>
# Una tupla di tipi di dati misti
student_info = ("Alice", 20, "Computer Science", 3.8)
print(student_info) # Output: ('Alice', 20, 'Computer Science', 3.8)
# Una tupla vuota
empty = ()
print(empty) # Output: ()
print(len(empty)) # Output: 0Le tuple usano le parentesi () per la loro sintassi letterale, mentre le liste usano le parentesi quadre []. Questa distinzione visiva ti aiuta a riconoscere subito con quale tipo stai lavorando.
È la virgola a creare la tupla, non le parentesi
Ecco un dettaglio cruciale che sorprende molti principianti: è la virgola a creare realmente una tupla, non le parentesi. Le parentesi spesso sono opzionali e servono principalmente a rendere la tupla più visibile o a raggrupparla nelle espressioni.
# Tutte queste creano la stessa tupla
coordinates_1 = (10, 20)
coordinates_2 = 10, 20 # Non servono parentesi!
print(coordinates_1) # Output: (10, 20)
print(coordinates_2) # Output: (10, 20)
print(coordinates_1 == coordinates_2) # Output: True
# È la virgola che conta
x = (42) # Questo è solo l'intero 42 tra parentesi
y = (42,) # Questa è una tupla che contiene un solo elemento
print(type(x)) # Output: <class 'int'>
print(type(y)) # Output: <class 'tuple'>
print(y) # Output: (42,)Le parentesi in (42) sono solo parentesi di raggruppamento, come nelle espressioni matematiche. Per creare una tupla con un solo elemento, devi includere una virgola finale: (42,). Questa virgola indica a Python che vuoi una tupla, non solo un’espressione raggruppata.
Quando le parentesi sono necessarie
Sebbene la virgola crei la tupla, le parentesi diventano necessarie in alcune situazioni per evitare ambiguità:
# Senza parentesi, questo sarebbe confuso
def get_dimensions():
return 1920, 1080 # Restituisce una tupla
width, height = get_dimensions()
print(f"Screen: {width}x{height}") # Output: Screen: 1920x1080
# Parentesi necessarie quando si passano tuple come argomenti di funzione
print((1, 2, 3)) # Output: (1, 2, 3)
# Senza parentesi, Python vedrebbe tre argomenti separati
# Parentesi necessarie in espressioni complesse
result = (10, 20) + (30, 40) # Concatenazione di tuple
print(result) # Output: (10, 20, 30, 40)Creare tuple con un solo elemento
Il requisito della virgola finale per le tuple con un solo elemento spesso coglie di sorpresa i principianti:
# Errore comune: dimenticare la virgola
not_a_tuple = ("Python")
print(type(not_a_tuple)) # Output: <class 'str'>
print(not_a_tuple) # Output: Python
# Corretto: includi la virgola finale
is_a_tuple = ("Python",)
print(type(is_a_tuple)) # Output: <class 'tuple'>
print(is_a_tuple) # Output: ('Python',)
# La virgola funziona anche senza parentesi
also_a_tuple = "Python",
print(type(also_a_tuple)) # Output: <class 'tuple'>
print(also_a_tuple) # Output: ('Python',)Perché Python richiede questa sintassi apparentemente scomoda? Perché le parentesi hanno già un altro significato in Python: raggruppano le espressioni. Senza la virgola, Python non ha modo di distinguere tra (42) come numero raggruppato e (42) come tupla.
Accedere agli elementi di una tupla
Le tuple supportano le stesse operazioni di indicizzazione e slicing delle liste:
# Tupla di informazioni di uno studente
student = ("Bob", 22, "Physics", 3.6)
# Accesso ai singoli elementi (indicizzazione da zero)
name = student[0]
age = student[1]
major = student[2]
gpa = student[3]
print(f"{name} is {age} years old") # Output: Bob is 22 years old
print(f"Major: {major}, GPA: {gpa}") # Output: Major: Physics, GPA: 3.6
# Funziona anche l'indicizzazione negativa
last_item = student[-1]
print(f"Last item: {last_item}") # Output: Last item: 3.6
# Lo slicing estrae una nuova tupla
first_two = student[:2]
print(first_two) # Output: ('Bob', 22)
print(type(first_two)) # Output: <class 'tuple'>Ogni tecnica di indicizzazione e slicing che hai imparato con le liste nel Capitolo 14 funziona in modo identico con le tuple. La differenza chiave è che le tuple non possono essere modificate dopo la creazione.
15.2) Packing e unpacking delle tuple
Una delle caratteristiche più potenti ed eleganti delle tuple è la loro capacità di raggruppare (packing) più valori e di distribuirli (unpacking) in variabili separate. Questa funzionalità rende il codice Python straordinariamente conciso e leggibile.
Packing delle tuple
Il tuple packing avviene quando crei una tupla mettendo insieme più valori, separati da virgole:
# Raggruppare valori in una tupla
coordinates = 10, 20, 30
print(coordinates) # Output: (10, 20, 30)
# Raggruppare tipi diversi
user_data = "Alice", 25, "alice@example.com"
print(user_data) # Output: ('Alice', 25, 'alice@example.com')
# Raggruppare valori restituiti da una funzione
def get_statistics(numbers):
total = sum(numbers)
count = len(numbers)
average = total / count
return total, count, average # Raggruppa tre valori in una tupla
stats = get_statistics([85, 90, 78, 92, 88])
print(stats) # Output: (433, 5, 86.6)Quando una funzione restituisce più valori separati da virgole, Python li raggruppa automaticamente in una tupla. Ecco perché le funzioni possono sembrare restituire più valori: in realtà stanno restituendo una singola tupla che contiene quei valori.
Unpacking delle tuple
Il tuple unpacking è il processo inverso: estrarre valori da una tupla in variabili separate:
# Unpacking di base
point = (100, 200)
x, y = point
print(f"x = {x}, y = {y}") # Output: x = 100, y = 200
# L'unpacking funziona con qualsiasi sequenza, non solo con le tuple
name, age, email = ["Bob", 30, "bob@example.com"]
print(f"{name} is {age} years old") # Output: Bob is 30 years old
# Unpacking diretto dei valori restituiti da una funzione
total, count, average = get_statistics([95, 88, 92, 85])
print(f"Average of {count} scores: {average}") # Output: Average of 4 scores: 90.0Il numero di variabili sul lato sinistro deve corrispondere al numero di elementi nella sequenza. Se non corrispondono, Python solleva un ValueError:
# Questo causerà un errore
coordinates = (10, 20, 30)
# x, y = coordinates # ValueError: too many values to unpack (expected 2)
# Anche questo causerà un errore
point = (5, 10)
# x, y, z = point # ValueError: not enough values to unpack (expected 3, got 2)Scambiare variabili con il tuple unpacking
Il tuple unpacking consente un modo elegante per scambiare i valori delle variabili senza dover usare una variabile temporanea:
# Scambio tradizionale usando una variabile temporanea
a = 10
b = 20
temp = a
a = b
b = temp
print(f"a = {a}, b = {b}") # Output: a = 20, b = 10
# Scambio elegante in Python usando il tuple unpacking
x = 100
y = 200
x, y = y, x # Scambia in una sola riga!
print(f"x = {x}, y = {y}") # Output: x = 200, y = 100
# Scambiare più di due variabili
first = "A"
second = "B"
third = "C"
first, second, third = third, first, second
print(first, second, third) # Output: C A BCome funziona? Python valuta prima il lato destro, creando una tupla (y, x), poi la “spacchetta” nelle variabili sul lato sinistro. Questo avviene in un unico passo, quindi non serve alcuna variabile temporanea.
Unpacking esteso con l’operatore stella
Python fornisce l’unpacking esteso usando l’operatore * per catturare più elementi:
# Unpacking con una variabile "resto"
scores = (95, 88, 92, 85, 90, 87)
first, second, *rest = scores
print(f"Top two: {first}, {second}") # Output: Top two: 95, 88
print(f"Others: {rest}") # Output: Others: [92, 85, 90, 87]
print(type(rest)) # Output: <class 'list'>
# La stella può comparire ovunque
numbers = (1, 2, 3, 4, 5)
first, *middle, last = numbers
print(f"First: {first}") # Output: First: 1
print(f"Middle: {middle}") # Output: Middle: [2, 3, 4]
print(f"Last: {last}") # Output: Last: 5
# Catturare l'inizio
*beginning, second_last, last = numbers
print(f"Beginning: {beginning}") # Output: Beginning: [1, 2, 3]
print(f"Last two: {second_last}, {last}") # Output: Last two: 4, 5Nota che la variabile con la stella cattura sempre gli elementi come una lista(list), anche quando fai unpacking da una tupla. Se non ci sono elementi da catturare, la variabile con la stella diventa una lista vuota:
# Quando non c'è nulla da catturare
a, b, *rest = (10, 20)
print(rest) # Output: []
# È consentita una sola stella per unpacking
# first, *middle, *end = (1, 2, 3, 4) # SyntaxError: multiple starred expressionsIgnorare valori con l’underscore
A volte ti servono solo alcuni valori di una tupla. Per convenzione, i programmatori Python usano l’underscore _ come nome di variabile per indicare i valori che vogliono ignorare:
# Analizzare una stringa di data
date_string = "2024-03-15"
year, month, day = date_string.split("-")
print(f"Month: {month}") # Output: Month: 03
# Se ci interessa solo il mese
_, month, _ = date_string.split("-")
print(f"Month: {month}") # Output: Month: 03
# Con unpacking esteso
data = ("Alice", 25, "Engineer", "New York", "alice@example.com")
name, age, *_, email = data
print(f"{name} ({age}): {email}") # Output: Alice (25): alice@example.comL’underscore è solo un normale nome di variabile, ma usarlo segnala ad altri programmatori (e a te stesso) che stai ignorando intenzionalmente quei valori.
Esempi pratici di packing e unpacking
# Restituire più valori dai calcoli
def calculate_rectangle_properties(width, height):
"""Calculate area and perimeter of a rectangle."""
area = width * height
perimeter = 2 * (width + height)
return area, perimeter # Packing
# Unpacking dei risultati
rect_area, rect_perimeter = calculate_rectangle_properties(5, 3)
print(f"Area: {rect_area}, Perimeter: {rect_perimeter}") # Output: Area: 15, Perimeter: 16
# Iterare con unpacking
students = [
("Alice", 85),
("Bob", 92),
("Carol", 78)
]
for name, score in students: # Unpacking nel ciclo
print(f"{name}: {score}")
# Output:
# Alice: 85
# Bob: 92
# Carol: 78Il packing e l’unpacking delle tuple rendono il codice Python più leggibile ed espressivo. Invece di accedere agli elementi della tupla per indice (student[0], student[1]), puoi spacchettarli in variabili con nomi significativi.
15.3) Le tuple sono immutabili: quando è utile
La caratteristica distintiva delle tuple è la loro immutabilità(immutability): una volta creata, il contenuto di una tupla non può essere cambiato. Non puoi aggiungere, rimuovere o modificare elementi. Questa immutabilità potrebbe sembrare una limitazione, ma offre vantaggi importanti.
Cosa significa l’immutabilità nella pratica
# Creare una tupla
coordinates = (10, 20, 30)
print(coordinates) # Output: (10, 20, 30)
# Tentare di modificare solleva un errore
# coordinates[0] = 15 # TypeError: 'tuple' object does not support item assignment
# Tentare di aggiungere elementi solleva un errore
# coordinates.append(40) # AttributeError: 'tuple' object has no attribute 'append'
# Tentare di rimuovere elementi solleva un errore
# del coordinates[1] # TypeError: 'tuple' object doesn't support item deletionQuando Python dice che le tuple non supportano l’assegnazione agli elementi, significa che non puoi cambiare cosa è memorizzato in una qualsiasi posizione della tupla. La struttura della tupla è fissata al momento della creazione.
Confrontare liste mutabili e tuple immutabili
# Le liste sono mutabili: puoi cambiarle
shopping_list = ["milk", "bread", "eggs"]
shopping_list[1] = "butter" # Modifica un elemento
shopping_list.append("cheese") # Aggiunge un elemento
print(shopping_list) # Output: ['milk', 'butter', 'eggs', 'cheese']
# Le tuple sono immutabili: non puoi cambiarle
product_dimensions = (10, 20, 5) # width, height, depth in cm
# product_dimensions[0] = 12 # TypeError: cannot modify
# product_dimensions.append(3) # AttributeError: no append method
# Per "cambiare" una tupla, devi crearne una nuova
new_dimensions = (12, 20, 5) # Crea una tupla completamente nuova
print(new_dimensions) # Output: (12, 20, 5)Perché l’immutabilità è utile
L’immutabilità offre diversi vantaggi pratici:
1. Integrità e sicurezza dei dati
Quando passi una tupla a una funzione(function), sai che la funzione non può modificare accidentalmente i tuoi dati:
def calculate_distance(point1, point2):
"""Calculate distance between two 2D points."""
x1, y1 = point1
x2, y2 = point2
dx = x2 - x1
dy = y2 - y1
# Anche se volessimo, non possiamo modificare le tuple di input
return (dx**2 + dy**2) ** 0.5
start = (0, 0)
end = (3, 4)
distance = calculate_distance(start, end)
print(f"Distance: {distance}") # Output: Distance: 5.0
print(f"Start point unchanged: {start}") # Output: Start point unchanged: (0, 0)Con le liste, dovresti preoccuparti se una funzione potrebbe modificare i tuoi dati. Con le tuple, hai la garanzia che non lo farà.
2. Usare le tuple come chiavi dei dizionari
Come esploreremo di più nel Capitolo 17, le chiavi dei dizionari devono essere hashable—devono avere un valore hash che non cambia mai. Oggetti immutabili come le tuple possono essere chiavi di dizionario; oggetti mutabili come le liste non possono:
# Le tuple possono essere chiavi di dizionario
locations = {
(0, 0): "Origin",
(10, 20): "Point A",
(30, 40): "Point B"
}
print(locations[(10, 20)]) # Output: Point A
# Le liste non possono essere chiavi di dizionario
# locations_bad = {
# [0, 0]: "Origin" # TypeError: unhashable type: 'list'
# }3. Segnalare l’intento
Usare una tupla invece di una lista comunica ad altri programmatori (e a te stesso) che questi dati non dovrebbero cambiare:
# Valori colore RGB: non dovrebbero mai cambiare
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# Parametri di connessione al database: configurazione fissa
DB_CONFIG = ("localhost", 5432, "myapp", "production")
# Coordinate geografiche: una posizione non cambia
EIFFEL_TOWER = (48.8584, 2.2945) # latitude, longitudeQuando vedi una tupla nel codice, sai subito che questi dati sono pensati per rimanere costanti. Quando vedi una lista, sai che potrebbe essere modificata.
4. Vantaggi di prestazioni
Poiché le tuple sono immutabili, Python può ottimizzarle in modi che non può applicare alle liste. Impareremo il modulo sys nel Capitolo 27, ma per ora sappi solo che sys.getsizeof() ci dice quanta memoria usa un oggetto:
import sys
# Le tuple usano meno memoria di liste equivalenti
tuple_data = (1, 2, 3, 4, 5)
list_data = [1, 2, 3, 4, 5]
print(f"Tuple size: {sys.getsizeof(tuple_data)} bytes") # Output: Tuple size: 80 bytes (may vary by Python version)
print(f"List size: {sys.getsizeof(list_data)} bytes") # Output: List size: 104 bytes (may vary by Python version)
# La creazione di tuple è più veloce
import timeit
tuple_time = timeit.timeit("(1, 2, 3, 4, 5)", number=1000000)
list_time = timeit.timeit("[1, 2, 3, 4, 5]", number=1000000)
print(f"Tuple creation: {tuple_time:.4f} seconds")
print(f"List creation: {list_time:.4f} seconds")
# Example output: Tuple creation: 0.0055 seconds, List creation: 0.0292 seconds15.4) La trappola dell’immutabilità: quando le tuple contengono elementi mutabili
Sebbene le tuple stesse siano immutabili, possono contenere oggetti mutabili come liste o dizionari. Questo crea una distinzione sottile ma importante: la struttura della tupla è fissa, ma il contenuto degli oggetti mutabili al suo interno può comunque cambiare.
Comprendere la distinzione
# Una tupla che contiene una lista
student_data = ("Alice", 20, [85, 90, 78]) # name, age, scores
print(student_data) # Output: ('Alice', 20, [85, 90, 78])
# Non possiamo riassegnare gli elementi della tupla
# student_data[0] = "Bob" # TypeError: 'tuple' object does not support item assignment
# Ma POSSIAMO modificare la lista dentro la tupla
student_data[2].append(92) # Aggiunge un nuovo punteggio
print(student_data) # Output: ('Alice', 20, [85, 90, 78, 92])
student_data[2][0] = 88 # Modifica un punteggio esistente
print(student_data) # Output: ('Alice', 20, [88, 90, 78, 92])Che cosa sta succedendo qui? La tupla memorizza tre riferimenti: uno alla stringa "Alice", uno all’intero 20 e uno a un oggetto lista. La struttura della tupla—quali oggetti essa referenzia—non può cambiare. Ma l’oggetto lista è mutabile, quindi il suo contenuto può cambiare.
Visualizzare la differenza
# La struttura della tupla è fissa
data = ("Python", [1, 2, 3])
# Questo prova a cambiare ciò che la tupla referenzia: NON CONSENTITO
# data[1] = [4, 5, 6] # TypeError
# Questo modifica la lista che la tupla referenzia: CONSENTITO
data[1].append(4)
print(data) # Output: ('Python', [1, 2, 3, 4])
# La tupla referenzia ancora lo stesso oggetto lista
# È cambiato solo il contenuto della lista, non quale lista la tupla puntaPensala così: una tupla è come una fila di scatole, e ogni scatola contiene un riferimento a un oggetto. Le scatole stesse sono bloccate al loro posto (immutabili), ma se una scatola contiene un riferimento a un oggetto mutabile, quell’oggetto può comunque cambiare.
Tuple con dizionari
Lo stesso principio si applica ai dizionari all’interno delle tuple:
# Tupla che contiene un dizionario
user_profile = ("alice", {"email": "alice@example.com", "age": 25})
print(user_profile) # Output: ('alice', {'email': 'alice@example.com', 'age': 25})
# Non possiamo cambiare quale dizionario la tupla referenzia
# user_profile[1] = {"email": "newemail@example.com"} # TypeError
# Ma POSSIAMO modificare il dizionario stesso
user_profile[1]["age"] = 26
user_profile[1]["city"] = "New York"
print(user_profile) # Output: ('alice', {'email': 'alice@example.com', 'age': 26, 'city': 'New York'})Perché questo conta per le chiavi dei dizionari
Le tuple possono essere usate come chiavi di dizionario solo se tutti i loro elementi sono hashable. Anche se le tuple stesse sono immutabili, una tupla che contiene oggetti mutabili (come le liste) non è hashable e quindi non può essere usata come chiave di dizionario.
# Questo approccio è pericoloso
tuple_with_list = ("key", [1, 2, 3])
# data = {tuple_with_list: "value"} # TypeError: unhashable type: 'list'Usa come chiavi di dizionario solo tuple che contengono oggetti completamente immutabili (stringhe, numeri, frozenset, altre tuple).
Creare tuple davvero immutabili
Se ti serve una tupla completamente immutabile, assicurati che anche tutto il suo contenuto sia immutabile:
# Tupla completamente immutabile: solo tipi immutabili
point_3d = (10, 20, 30) # Tutti interi
rgb_color = (255, 128, 0) # Tutti interi
coordinates = ((10, 20), (30, 40)) # Tupla di tuple
# Queste sono sicure da usare come chiavi di dizionario
color_names = {
(255, 0, 0): "Red",
(0, 255, 0): "Green",
(0, 0, 255): "Blue"
}
# Le tuple annidate restano immutabili
nested = ((1, 2), (3, 4))
# nested[0][0] = 5 # TypeError: 'tuple' object does not support item assignmentQuando i contenuti mutabili sono intenzionali
A volte vuoi davvero una tupla con contenuti mutabili—per esempio, quando hai una struttura di record fissa ma un campo deve cambiare:
# Record studente con identità fissa ma voti che cambiano
def create_student(name, student_id):
"""Create a student record with empty grade list."""
return (name, student_id, []) # name e ID fissi, i voti possono cambiare
student = create_student("Alice", "S12345")
print(student) # Output: ('Alice', 'S12345', [])
# L'identità dello studente è fissa
print(f"Student: {student[0]} (ID: {student[1]})") # Output: Student: Alice (ID: S12345)
# Ma possiamo aggiungere voti man mano che vengono ottenuti
student[2].append(85)
student[2].append(92)
student[2].append(78)
print(f"Grades: {student[2]}") # Output: Grades: [85, 92, 78]
# La struttura della tupla protegge nome e ID da cambi accidentali
# consentendo allo stesso tempo alla lista dei voti di crescereQuesto schema è utile quando vuoi proteggere alcuni dati consentendo ad altri di cambiare. Tieni solo presente la distinzione tra l’immutabilità della tupla e la mutabilità del suo contenuto.
15.5) Quando usare le tuple invece delle liste
Scegliere tra tuple e liste è un’importante decisione di progettazione. Anche se entrambe sono sequenze, servono scopi diversi e comunicano intenzioni differenti.
Usa le tuple per dati fissi ed eterogenei
Le tuple funzionano meglio quando hai un numero fisso di elementi che rappresentano una singola entità logica, spesso con tipi diversi:
# Record studente: nome, età, corso di laurea, GPA
student = ("Alice", 20, "Computer Science", 3.8)
# Coordinate geografiche: latitudine, longitudine
location = (40.7128, -74.0060) # New York City
# Colore RGB: rosso, verde, blu
color = (255, 128, 0)
# Connessione database: host, porta, database, username
db_connection = ("localhost", 5432, "myapp", "admin")
# Data: anno, mese, giorno
date = (2024, 3, 15)Ogni tupla rappresenta un “record” completo in cui la posizione di ciascun elemento ha un significato specifico. Il primo elemento è sempre il nome, il secondo è sempre l’età, e così via.
Usa le liste per collezioni omogenee
Le liste funzionano meglio quando hai un numero variabile di elementi simili che potresti aggiungere, rimuovere o riordinare:
# Lista della spesa: elementi dello stesso tipo (stringhe)
shopping_list = ["milk", "bread", "eggs", "butter"]
shopping_list.append("cheese") # Aggiungi altri elementi quando serve
shopping_list.remove("bread") # Rimuovi elementi
# Punteggi dei test: elementi dello stesso tipo (numeri)
test_scores = [85, 92, 78, 95, 88]
test_scores.append(90) # Aggiungi un nuovo punteggio
test_scores.sort() # Riordina i punteggi
# Nomi utente: elementi dello stesso tipo (stringhe)
active_users = ["alice", "bob", "carol"]
active_users.extend(["dave", "eve"]) # Aggiungi più utentiLe liste sono per collezioni in cui il numero di elementi può cambiare e in cui ogni elemento svolge lo stesso ruolo.
Tuple per valori di ritorno delle funzioni
Quando una funzione restituisce più valori correlati, le tuple sono la scelta naturale:
def get_user_info(user_id):
"""Retrieve user information from database."""
# Simulate database lookup
return "Alice", "alice@example.com", 25, "New York"
# Unpacking della tupla restituita
name, email, age, city = get_user_info(101)
print(f"{name} from {city}") # Output: Alice from New York
def calculate_statistics(numbers):
"""Calculate min, max, and average of numbers."""
if not numbers:
return None, None, None
minimum = min(numbers)
maximum = max(numbers)
average = sum(numbers) / len(numbers)
return minimum, maximum, average
# Unpacking dei risultati
min_val, max_val, avg_val = calculate_statistics([85, 92, 78, 95, 88])
print(f"Range: {min_val} to {max_val}, Average: {avg_val}")
# Output: Range: 78 to 95, Average: 87.6Restituire tuple rende chiaro che questi valori sono correlati e dovrebbero essere considerati insieme.
Tuple come chiavi di dizionario
Quando ti servono chiavi composte in un dizionario, le tuple sono essenziali:
# Voti degli studenti per corso e semestre
grades = {
("CS101", "Fall2023"): 85,
("CS101", "Spring2024"): 90,
("MATH201", "Fall2023"): 88,
("MATH201", "Spring2024"): 92
}
# Cerca un voto specifico
course = "CS101"
semester = "Spring2024"
grade = grades[(course, semester)]
print(f"Grade in {course} ({semester}): {grade}") # Output: Grade in CS101 (Spring2024): 90
# Coordinate di una griglia come chiavi di dizionario
grid = {
(0, 0): "Start",
(5, 3): "Obstacle",
(10, 10): "Goal"
}
position = (5, 3)
if position in grid:
print(f"At {position}: {grid[position]}") # Output: At (5, 3): ObstacleLe liste non possono essere chiavi di dizionario perché sono mutabili, ma le tuple possono.
Tuple per configurazioni immutabili
Quando hai dati di configurazione che non dovrebbero mai cambiare, le tuple segnalano questo intento:
# Impostazioni dell'applicazione che dovrebbero restare costanti
APP_CONFIG = (
"MyApp", # Nome applicazione
"1.0.0", # Versione
"production", # Ambiente
True, # Modalità debug
8080 # Porta
)
# Palette di colori per UI: questi colori sono fissi
COLOR_PALETTE = (
(255, 0, 0), # Rosso primario
(0, 128, 255), # Blu primario
(255, 255, 255), # Bianco
(0, 0, 0) # Nero
)
# Endpoint API: questi URL non cambiano
API_ENDPOINTS = (
"https://api.example.com/users",
"https://api.example.com/products",
"https://api.example.com/orders"
)Guida decisionale
# Usa le TUPLE quando:
# 1. I dati rappresentano un singolo record con struttura fissa
employee = ("E001", "Alice", "Engineering", 75000)
# 2. Restituisci più valori da una funzione
def divide_with_remainder(a, b):
return a // b, a % b
# 3. Devi usarle come chiavi di dizionario
cache = {(5, 10): 50, (3, 7): 21}
# 4. I dati non devono essere modificati
SCREEN_RESOLUTION = (1920, 1080)
# Usa le LISTE quando:
# 1. Collezione di elementi simili che potrebbero cambiare
tasks = ["Write code", "Test code", "Deploy code"]
tasks.append("Document code")
# 2. Devi aggiungere, rimuovere o riordinare elementi
scores = [85, 90, 78]
scores.sort()
scores.append(92)
# 3. Tutti gli elementi hanno lo stesso scopo
usernames = ["alice", "bob", "carol"]
# 4. La dimensione della collezione non è nota in anticipo
results = []
for i in range(10):
results.append(i * 2)15.6) Comprendere a fondo gli oggetti range
Ora che abbiamo capito quando usare le tuple rispetto alle liste, esploriamo il terzo tipo di sequenza immutabile di Python: i range. Il tipo range(range) rappresenta una sequenza immutabile di numeri. A differenza di liste e tuple che memorizzano tutti i loro elementi in memoria, gli oggetti range generano numeri su richiesta, rendendoli estremamente efficienti in termini di memoria per rappresentare sequenze di grandi dimensioni.
Creare oggetti range
La funzione range() crea oggetti range in tre forme:
# Un solo argomento: range(stop)
# Genera numeri da 0 fino a (ma senza includere) stop
numbers = range(5)
print(list(numbers)) # Output: [0, 1, 2, 3, 4]
# Due argomenti: range(start, stop)
# Genera numeri da start fino a (ma senza includere) stop
numbers = range(2, 7)
print(list(numbers)) # Output: [2, 3, 4, 5, 6]
# Tre argomenti: range(start, stop, step)
# Genera numeri da start fino a stop, incrementando di step
numbers = range(0, 10, 2)
print(list(numbers)) # Output: [0, 2, 4, 6, 8]
# Step negativo per contare all'indietro
numbers = range(10, 0, -1)
print(list(numbers)) # Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]Nota che convertiamo i range in liste con list() per vederne il contenuto. Un oggetto range in sé non mostra tutti i suoi valori quando viene stampato:
r = range(5)
print(r) # Output: range(0, 5)
print(type(r)) # Output: <class 'range'>Come funzionano gli oggetti range
Gli oggetti range non memorizzano tutti i loro valori in memoria. Invece, calcolano ciascun valore quando serve:
import sys
# Un range che rappresenta un milione di numeri
large_range = range(1000000)
print(f"Range size: {sys.getsizeof(large_range)} bytes") # Output: Range size: 48 bytes (may vary by Python version)
# Una lista che contiene un milione di numeri
large_list = list(range(1000000))
print(f"List size: {sys.getsizeof(large_list)} bytes") # Output: List size: 8000056 bytes (approximately 8MB)
# Il range è minuscolo; la lista è enorme!Un oggetto range memorizza solo tre valori: start, stop e step. Calcola ogni numero della sequenza quando glielo chiedi. Questo rende i range incredibilmente efficienti per sequenze grandi.
Usare range nei cicli for
Come abbiamo imparato nel Capitolo 12, i range sono usati più comunemente con i cicli(loop) for:
# Contare da 0 a 4
for i in range(5):
print(f"Count: {i}")
# Output:
# Count: 0
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Contare da 1 a 10
for i in range(1, 11):
print(i, end=" ")
print() # Output: 1 2 3 4 5 6 7 8 9 10
# Contare a passi di due
for i in range(0, 20, 2):
print(i, end=" ")
print() # Output: 0 2 4 6 8 10 12 14 16 18
# Contare all'indietro
for i in range(5, 0, -1):
print(f"T-minus {i}")
# Output:
# T-minus 5
# T-minus 4
# T-minus 3
# T-minus 2
# T-minus 1Indicizzazione e slicing degli oggetti range
Gli oggetti range supportano indicizzazione e slicing proprio come le altre sequenze:
# Creare un range
numbers = range(10, 50, 5) # 10, 15, 20, 25, 30, 35, 40, 45
# Indicizzazione
print(numbers[0]) # Output: 10
print(numbers[3]) # Output: 25
print(numbers[-1]) # Output: 45
# Lo slicing restituisce un nuovo range
subset = numbers[2:5]
print(subset) # Output: range(20, 35, 5)
print(list(subset)) # Output: [20, 25, 30]
# Lunghezza
print(len(numbers)) # Output: 8Verificare l’appartenenza
Puoi controllare se un numero è in un range usando l’operatore in:
# Numeri pari da 0 a 20
evens = range(0, 21, 2)
print(10 in evens) # Output: True
print(15 in evens) # Output: False
print(20 in evens) # Output: True
# Questo è molto efficiente: Python non genera tutti i numeri
# Calcola se il numero sarebbe nella sequenza
large_range = range(0, 1000000, 3)
print(999999 in large_range) # Output: True (instant, no iteration needed)Python può determinare l’appartenenza matematicamente senza generare tutti i numeri, rendendo questa operazione estremamente veloce anche per range enormi.
Range vuoti e range inversi
# Range vuoto: stop uguale a start
empty = range(5, 5)
print(list(empty)) # Output: []
print(len(empty)) # Output: 0
# Range vuoto: impossibile raggiungere stop con lo step dato
impossible = range(1, 10, -1) # Non si può contare in avanti con step negativo
print(list(impossible)) # Output: []
# Range inverso
backwards = range(10, 0, -1)
print(list(backwards)) # Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
# Inverso con numeri negativi
negative_range = range(-5, -15, -2)
print(list(negative_range)) # Output: [-5, -7, -9, -11, -13]Quando usare range vs liste
# Usa range quando:
# 1. Ti serve una sequenza di numeri per iterare
for i in range(100):
# Elabora qualcosa 100 volte
pass
# 2. Ti servono indici per una sequenza
items = ["a", "b", "c", "d"]
for i in range(len(items)):
print(f"Index {i}: {items[i]}")
# 3. L'efficienza di memoria conta con sequenze grandi
# Questo usa memoria minima
for i in range(1000000):
if i % 100000 == 0:
print(i)
# Usa le liste quando:
# 1. Devi memorizzare i valori reali
squares = [1, 3, 5, 7, 10]
# 2. Devi modificare la sequenza
numbers = list(range(5))
numbers[2] = 100 # Modifica un valore
numbers.append(200) # Aggiunge un valore
# 3. Devi usare la sequenza più volte con operazioni diverse
data = list(range(10))
print(sum(data))
print(max(data))
print(sorted(data, reverse=True))Gli oggetti range sono un esempio perfetto dell’efficienza di Python. Offrono tutti i vantaggi di una sequenza senza il costo di memoria di memorizzare ogni elemento.
15.7) Convertire tra liste, tuple e range
Python rende facile convertire tra tipi di sequenza diversi. Capire queste conversioni ti aiuta a scegliere il tipo giusto per ogni situazione e a trasformare i dati quando serve.
Convertire in liste
La funzione list() converte qualsiasi sequenza in una lista:
# Tupla in lista
student_tuple = ("Alice", 20, "CS")
student_list = list(student_tuple)
print(student_list) # Output: ['Alice', 20, 'CS']
print(type(student_list)) # Output: <class 'list'>
# Ora possiamo modificarla
student_list[1] = 21
student_list.append(3.8)
print(student_list) # Output: ['Alice', 21, 'CS', 3.8]
# Range in lista
numbers = range(5)
numbers_list = list(numbers)
print(numbers_list) # Output: [0, 1, 2, 3, 4]
# Stringa in lista (ogni carattere diventa un elemento)
text = "Python"
chars = list(text)
print(chars) # Output: ['P', 'y', 't', 'h', 'o', 'n']Convertire in una lista è utile quando devi modificare una sequenza o quando devi usare metodi specifici delle liste come append(), sort() o remove().
Convertire in tuple
La funzione tuple() converte qualsiasi sequenza in una tupla:
# Lista in tupla
scores_list = [85, 90, 78, 92]
scores_tuple = tuple(scores_list)
print(scores_tuple) # Output: (85, 90, 78, 92)
print(type(scores_tuple)) # Output: <class 'tuple'>
# Ora è immutabile
# scores_tuple[0] = 88 # TypeError: 'tuple' object does not support item assignment
# Range in tupla
numbers = range(1, 6)
numbers_tuple = tuple(numbers)
print(numbers_tuple) # Output: (1, 2, 3, 4, 5)
# Stringa in tupla
text = "Hi"
chars_tuple = tuple(text)
print(chars_tuple) # Output: ('H', 'i')Convertire in una tupla è utile quando vuoi proteggere i dati dalle modifiche o quando devi usare una sequenza come chiave di dizionario.
15.8) Operazioni comuni sulle sequenze tra stringhe, liste, tuple e range
I tipi di sequenza di Python—stringhe, liste, tuple e range—condividono molte operazioni comuni. Comprendere queste operazioni condivise ti aiuta a lavorare in modo efficiente con qualsiasi tipo di sequenza.
Lunghezza, minimo e massimo
Tutte le sequenze supportano le funzioni len(), min() e max():
# Stringhe
text = "Python"
print(len(text)) # Output: 6
print(min(text)) # Output: P (smallest character by Unicode value)
print(max(text)) # Output: y (largest character by Unicode value)
# Liste
numbers = [45, 12, 78, 23, 56]
print(len(numbers)) # Output: 5
print(min(numbers)) # Output: 12
print(max(numbers)) # Output: 78
# Tuple
scores = (85, 92, 78, 95, 88)
print(len(scores)) # Output: 5
print(min(scores)) # Output: 78
print(max(scores)) # Output: 95
# Range
nums = range(10, 50, 5)
print(len(nums)) # Output: 8
print(min(nums)) # Output: 10
print(max(nums)) # Output: 45Perché min() e max() funzionino, gli elementi devono essere confrontabili. Non puoi trovare il minimo di una lista che contiene sia stringhe sia numeri:
mixed = [1, "hello", 3]
# print(min(mixed)) # TypeError: '<' not supported between instances of 'str' and 'int'Indicizzazione e indicizzazione negativa
Tutte le sequenze supportano l’indicizzazione con indici positivi e negativi:
# Indicizzazione positiva (basata su 0)
text = "Python"
numbers = [10, 20, 30, 40, 50]
coords = (5, 10, 15)
values = range(0, 100, 10)
print(text[0]) # Output: P
print(numbers[2]) # Output: 30
print(coords[1]) # Output: 10
print(values[3]) # Output: 30
# Indicizzazione negativa (dalla fine)
print(text[-1]) # Output: n (last character)
print(numbers[-2]) # Output: 40 (second from end)
print(coords[-3]) # Output: 5 (third from end, which is first)
print(values[-1]) # Output: 90 (last value in range)Gli indici negativi contano dalla fine: -1 è l’ultimo elemento, -2 è il penultimo, e così via.
Test di appartenenza con in e not in
Tutte le sequenze supportano i test di appartenenza:
# Stringhe: controlla sottostringhe
text = "Python Programming"
print("Python" in text) # Output: True
print("Java" in text) # Output: False
print("gram" in text) # Output: True (substring)
print("PYTHON" not in text) # Output: True (case-sensitive)
# Liste
fruits = ["apple", "banana", "cherry", "date"]
print("banana" in fruits) # Output: True
print("grape" in fruits) # Output: False
print("apple" not in fruits) # Output: False
# Tuple
coordinates = (10, 20, 30, 40)
print(20 in coordinates) # Output: True
print(25 in coordinates) # Output: False
print(50 not in coordinates) # Output: True
# Range: molto efficiente, non serve iterare
numbers = range(0, 100, 2) # Even numbers 0 to 98
print(50 in numbers) # Output: True
print(51 in numbers) # Output: False (odd number)
print(100 in numbers) # Output: False (stop is exclusive)Per i range, Python può determinare l’appartenenza matematicamente senza controllare ogni elemento, rendendola estremamente veloce anche per range enormi.
Concatenazione e ripetizione
Stringhe, liste e tuple supportano la concatenazione con + e la ripetizione con *:
# Concatenazione con +
text1 = "Hello"
text2 = " World"
print(text1 + text2) # Output: Hello World
list1 = [1, 2, 3]
list2 = [4, 5, 6]
print(list1 + list2) # Output: [1, 2, 3, 4, 5, 6]
tuple1 = (10, 20)
tuple2 = (30, 40)
print(tuple1 + tuple2) # Output: (10, 20, 30, 40)
# Ripetizione con *
print("Ha" * 3) # Output: HaHaHa
print([0] * 5) # Output: [0, 0, 0, 0, 0]
print((1, 2) * 3) # Output: (1, 2, 1, 2, 1, 2)Importante: i range non supportano concatenazione o ripetizione:
r1 = range(5)
r2 = range(5, 10)
# combined = r1 + r2 # TypeError: unsupported operand type(s) for +: 'range' and 'range'
# Per combinare range, converti prima in liste o tuple
combined = list(r1) + list(r2)
print(combined) # Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]Contare le occorrenze
Il metodo count() restituisce quante volte un elemento compare:
# Stringhe: conta occorrenze di sottostringhe
text = "Mississippi"
print(text.count("s")) # Output: 4
print(text.count("ss")) # Output: 2
print(text.count("i")) # Output: 4
# Liste
numbers = [1, 2, 3, 2, 4, 2, 5]
print(numbers.count(2)) # Output: 3
print(numbers.count(6)) # Output: 0
# Tuple
grades = (85, 90, 85, 92, 85, 88)
print(grades.count(85)) # Output: 3
print(grades.count(95)) # Output: 0
# I range non hanno il metodo count(), ma puoi convertire prima
nums = range(0, 20, 2)
nums_list = list(nums)
print(nums_list.count(10)) # Output: 1Trovare l’indice degli elementi
Il metodo index() restituisce la posizione della prima occorrenza:
# Stringhe
text = "Python Programming"
print(text.index("P")) # Output: 0 (first P)
print(text.index("Pro")) # Output: 7 (substring position)
# print(text.index("Java")) # ValueError: substring not found
# Liste
fruits = ["apple", "banana", "cherry", "banana"]
print(fruits.index("banana")) # Output: 1 (first occurrence)
print(fruits.index("cherry")) # Output: 2
# print(fruits.index("grape")) # ValueError: 'grape' is not in list
# Tuple
coordinates = (10, 20, 30, 20, 40)
print(coordinates.index(20)) # Output: 1 (first occurrence)
print(coordinates.index(40)) # Output: 4
# I range non hanno il metodo index(), ma puoi convertire prima
nums = range(10, 50, 5)
nums_list = list(nums)
print(nums_list.index(25)) # Output: 3Se l’elemento non viene trovato, index() solleva un ValueError. Per evitarlo, controlla prima con in:
fruits = ["apple", "banana", "cherry"]
search_fruit = "grape"
if search_fruit in fruits:
position = fruits.index(search_fruit)
print(f"{search_fruit} found at position {position}")
else:
print(f"{search_fruit} not found")
# Output: grape not foundIterazione con cicli for
Tutte le sequenze possono essere iterate con i cicli(loop) for:
# Stringhe: itera sui caratteri
for char in "Python":
print(char, end=" ")
print() # Output: P y t h o n
# Liste
for fruit in ["apple", "banana", "cherry"]:
print(f"I like {fruit}")
# Output:
# I like apple
# I like banana
# I like cherry
# Tuple
for score in (85, 90, 78):
print(f"Score: {score}")
# Output:
# Score: 85
# Score: 90
# Score: 78
# Range
for i in range(1, 6):
print(f"Count: {i}")
# Output:
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Count: 5Operazioni di confronto
Le sequenze possono essere confrontate usando ==, !=, <, >, <= e >=:
# Uguaglianza
print([1, 2, 3] == [1, 2, 3]) # Output: True
print((1, 2, 3) == (1, 2, 3)) # Output: True
print("abc" == "abc") # Output: True
# Disuguaglianza
print([1, 2, 3] != [1, 2, 4]) # Output: True
print((1, 2) != (1, 2)) # Output: False
# Confronto lessicografico (elemento per elemento)
print([1, 2, 3] < [1, 2, 4]) # Output: True (3 < 4)
print([1, 2, 3] < [1, 3, 0]) # Output: True (2 < 3)
print("apple" < "banana") # Output: True (alphabetical)
print((1, 2) < (1, 2, 3)) # Output: True (shorter is less if equal so far)
# Confrontare tipi diversi
print([1, 2, 3] == (1, 2, 3)) # Output: False (different types)Il confronto funziona elemento per elemento da sinistra a destra. Il primo elemento diverso determina il risultato.
Comprendere queste operazioni comuni ti permette di scrivere codice che funziona con qualsiasi tipo di sequenza, rendendo i tuoi programmi più flessibili e riutilizzabili.
15.9) Slicing avanzato su tutti i tipi di sequenza
Lo slicing è una delle funzionalità più potenti di Python per lavorare con le sequenze. Anche se abbiamo introdotto lo slicing di base nel Capitolo 14, ci sono tecniche avanzate di slicing che funzionano su tutti i tipi di sequenza.
Ripasso dello slicing di base
Lo slicing estrae una porzione di una sequenza usando la sintassi sequence[start:stop:step]:
# Slicing di base con le stringhe
text = "Python Programming"
print(text[0:6]) # Output: Python
print(text[7:18]) # Output: Programming
print(text[7:]) # Output: Programming (from index 7 to end)
print(text[:6]) # Output: Python (from start to index 6)
# Slicing di base con le liste
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:7]) # Output: [2, 3, 4, 5, 6]
print(numbers[:5]) # Output: [0, 1, 2, 3, 4]
print(numbers[5:]) # Output: [5, 6, 7, 8, 9]
# Slicing di base con le tuple
coordinates = (10, 20, 30, 40, 50, 60)
print(coordinates[1:4]) # Output: (20, 30, 40)
print(coordinates[:3]) # Output: (10, 20, 30)
print(coordinates[3:]) # Output: (40, 50, 60)
# Slicing di base con i range
nums = range(0, 100, 10)
print(list(nums[2:5])) # Output: [20, 30, 40]Ricorda: start è incluso, stop è escluso, e il risultato è sempre dello stesso tipo della sequenza originale.
Usare step nello slicing
Il terzo parametro opzionale step controlla quanti elementi saltare:
# Ogni secondo elemento
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::2]) # Output: [0, 2, 4, 6, 8]
print(numbers[1::2]) # Output: [1, 3, 5, 7, 9]
# Ogni terzo elemento
text = "abcdefghijklmnop"
print(text[::3]) # Output: adgjmp
# Step con start e stop
print(numbers[2:8:2]) # Output: [2, 4, 6]
print(text[1:10:2]) # Output: bdfhjStep negativo: invertire le sequenze
Uno step negativo inverte la direzione dello slicing:
# Invertire sequenze intere
text = "Python"
print(text[::-1]) # Output: nohtyP
numbers = [1, 2, 3, 4, 5]
print(numbers[::-1]) # Output: [5, 4, 3, 2, 1]
coordinates = (10, 20, 30, 40)
print(coordinates[::-1]) # Output: (40, 30, 20, 10)
# Invertire con step
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::-2]) # Output: [9, 7, 5, 3, 1] (every second, backwards)
# Invertire una porzione
text = "Python Programming"
print(text[7:18][::-1]) # Output: gnimmargorP (reverse "Programming")Quando usi step negativo, start e stop funzionano in modo diverso:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Con step negativo, start dovrebbe essere maggiore di stop
print(numbers[7:2:-1]) # Output: [7, 6, 5, 4, 3] (from 7 down to 3)
print(numbers[8:3:-2]) # Output: [8, 6, 4] (from 8 down to 4, step -2)
# Omettere start/stop con step negativo
print(numbers[:5:-1]) # Output: [9, 8, 7, 6] (from end down to 6)
print(numbers[5::-1]) # Output: [5, 4, 3, 2, 1, 0] (from 5 down to start)Indici negativi nello slicing
Puoi usare indici negativi per le posizioni start e stop:
text = "Python Programming"
# Ultimi 11 caratteri
print(text[-11:]) # Output: Programming
# Tutto tranne gli ultimi 11 caratteri
print(text[:-11]) # Output: Python
# Da -15 a -5
print(text[-15:-5]) # Output: hon Progra
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Ultimi 5 elementi
print(numbers[-5:]) # Output: [5, 6, 7, 8, 9]
# Tutto tranne gli ultimi 3 elementi
print(numbers[:-3]) # Output: [0, 1, 2, 3, 4, 5, 6]
# Da -7 a -2
print(numbers[-7:-2]) # Output: [3, 4, 5, 6, 7]Slicing con i range
Lo slicing di un range restituisce un nuovo oggetto range:
# Slicing di range
numbers = range(0, 100, 5) # 0, 5, 10, 15, ..., 95
print(numbers) # Output: range(0, 100, 5)
# Lo slice restituisce un nuovo range
subset = numbers[5:10]
print(subset) # Output: range(25, 50, 5)
print(list(subset)) # Output: [25, 30, 35, 40, 45]
# Con step
every_other = numbers[::2]
print(list(every_other)) # Output: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
# Step negativo
reversed_range = numbers[::-1]
print(list(reversed_range)) # Output: [95, 90, 85, ..., 5, 0]Slice vuoti e casi limite
numbers = [1, 2, 3, 4, 5]
# Slice vuoti (start >= stop con step positivo)
print(numbers[3:3]) # Output: []
print(numbers[5:10]) # Output: [] (stop beyond length)
print(numbers[10:20]) # Output: [] (both beyond length)
# Gli slice oltre i limiti della sequenza sono sicuri
print(numbers[-100:100]) # Output: [1, 2, 3, 4, 5] (entire sequence)
print(numbers[2:100]) # Output: [3, 4, 5] (from 2 to end)
# Step negativo con start/stop incompatibili
print(numbers[2:7:-1]) # Output: [] (can't go forward with negative step)
# Uno step di 0 non è consentito
# print(numbers[::0]) # ValueError: slice step cannot be zeroSlicing per copiare
Lo slicing crea una nuova sequenza, offrendo un modo per copiare:
# Copiare con lo slicing
original = [1, 2, 3, 4, 5]
copy = original[:] # Slice dall'inizio alla fine
print(copy) # Output: [1, 2, 3, 4, 5]
# Modificare la copia non influisce sull'originale
copy[0] = 100
print(f"Original: {original}") # Output: Original: [1, 2, 3, 4, 5]
print(f"Copy: {copy}") # Output: Copy: [100, 2, 3, 4, 5]
# Funziona anche per le tuple (crea una nuova tupla)
original_tuple = (1, 2, 3, 4, 5)
copy_tuple = original_tuple[:]
print(copy_tuple) # Output: (1, 2, 3, 4, 5)
# Per le stringhe
text = "Python"
text_copy = text[:]
print(text_copy) # Output: PythonTuttavia, ricorda dal Capitolo 14 che questo crea una copia superficiale(shallow copy).
# Limite della copia superficiale
original = [[1, 2], [3, 4]]
copy = original[:]
# Modificare una lista annidata influenza entrambe
copy[0][0] = 100
print(f"Original: {original}") # Output: Original: [[100, 2], [3, 4]]
print(f"Copy: {copy}") # Output: Copy: [[100, 2], [3, 4]]Le tuple e i range sono strumenti essenziali nel kit delle sequenze di Python. Le tuple forniscono dati strutturati e immutabili che proteggono le informazioni da modifiche accidentali e ne consentono l’uso come chiavi di dizionario. I range offrono rappresentazioni efficienti in termini di memoria delle sequenze di numeri, perfette per i cicli e per sequenze grandi. Capire quando usare ciascun tipo—e come convertire tra di essi—rende il tuo codice più efficiente, più sicuro e più chiaro nell’intento.
Le operazioni comuni condivise tra tutti i tipi di sequenza—indicizzazione, slicing, iterazione, test di appartenenza—formano un’interfaccia coerente che rende intuitivo lavorare con qualsiasi sequenza. Le tecniche avanzate di slicing ti offrono modi potenti ed espressivi per estrarre e manipolare dati di sequenza.
Man mano che continui a programmare in Python, ti ritroverai a scegliere naturalmente il tipo di sequenza giusto per ogni situazione: liste per collezioni che cambiano, tuple per record fissi, range per sequenze di numeri e stringhe per il testo. Questo capitolo ti ha dato le conoscenze per fare queste scelte con sicurezza e usare ciascun tipo in modo efficace.