Python & AI Tutorials Logo
Programmazione Python

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:

python
# 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: 0

Le 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.

python
# 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à:

python
# 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:

python
# 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:

python
# 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.

Più elementi

Elemento singolo

Vuota

Creazione di tuple

Scelta della sintassi

(item1, item2, ...)

(item,) - virgola obbligatoria

()

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:

python
# 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:

python
# 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.0

Il numero di variabili sul lato sinistro deve corrispondere al numero di elementi nella sequenza. Se non corrispondono, Python solleva un ValueError:

python
# 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:

python
# 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 B

Come 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:

python
# 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, 5

Nota 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:

python
# 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 expressions

Ignorare 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:

python
# 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.com

L’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

python
# 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: 78

Il 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

python
# 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 deletion

Quando 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

python
# 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:

python
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:

python
# 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:

python
# 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, longitude

Quando 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:

python
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 seconds

15.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

python
# 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

python
# 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 punta

Pensala 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:

python
# 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.

python
# 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:

python
# 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 assignment

Quando 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:

python
# 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 crescere

Questo 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:

python
# 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:

python
# 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ù utenti

Le 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:

python
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.6

Restituire 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:

python
# 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): Obstacle

Le 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:

python
# 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

python
# 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:

python
# 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:

python
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:

python
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:

python
# 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 1

Indicizzazione e slicing degli oggetti range

Gli oggetti range supportano indicizzazione e slicing proprio come le altre sequenze:

python
# 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: 8

Verificare l’appartenenza

Puoi controllare se un numero è in un range usando l’operatore in:

python
# 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

python
# 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

python
# 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:

python
# 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:

python
# 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.

list

tuple

tuple

list

Range

Lista

Tupla

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():

python
# 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: 45

Perché min() e max() funzionino, gli elementi devono essere confrontabili. Non puoi trovare il minimo di una lista che contiene sia stringhe sia numeri:

python
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:

python
# 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:

python
# 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 *:

python
# 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:

python
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:

python
# 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: 1

Trovare l’indice degli elementi

Il metodo index() restituisce la posizione della prima occorrenza:

python
# 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: 3

Se l’elemento non viene trovato, index() solleva un ValueError. Per evitarlo, controlla prima con in:

python
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 found

Iterazione con cicli for

Tutte le sequenze possono essere iterate con i cicli(loop) for:

python
# 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: 5

Operazioni di confronto

Le sequenze possono essere confrontate usando ==, !=, <, >, <= e >=:

python
# 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]:

python
# 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:

python
# 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: bdfhj

Step negativo: invertire le sequenze

Uno step negativo inverte la direzione dello slicing:

python
# 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:

python
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:

python
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:

python
# 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

python
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 zero

Slicing per copiare

Lo slicing crea una nuova sequenza, offrendo un modo per copiare:

python
# 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: Python

Tuttavia, ricorda dal Capitolo 14 che questo crea una copia superficiale(shallow copy).

python
# 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.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai