Salta al contenuto
Note per Studenti Esercizio 4 · riferimenti, copie, None e indici

Esercizio 4riferimenti, copie, None e indici

Esame
In questa pagina 8

Testo (questionari di novembre e dicembre 2022 e test d'esame di gennaio 2014 del corso di Fondamenti di Informatica UniPD; adattato da un tema d'esame in Java, dove gli errori sono NullPointerException e ArrayIndexOutOfBoundsException). Per ciascun frammento dire che errore segnala (o che cosa stampa) e perché. Teoria: Variabili, oggetti e riferimentiIn Python una variabile è un nome legato a un oggetto; tipo, identità e valore; assegnamento, aliasing, oggetti mutabili e immutabili, None.Variabili, oggetti e riferimenti →, Liste in Pythonlist come sequenza mutabile (array dinamico); indici, slicing e assegnamento a fette; metodi e loro costo; aliasing, copia superficiale e profonda; list comprehension; liste annidate e matrici.Liste in Python →, Funzioni in Pythondef e return, parametri posizionali, con nome e con valore predefinito, *args e **kwargs; passaggio per riferimento a oggetto; ambito delle variabili LEGB; docstring, type hint, lambda.Funzioni in Python →.

python
class Complessa:
    def __init__(self):
        self.reale = 0
        self.imm = 0

# (a)
v = [None] * 10
for i in range(10):
    v[i].reale = 0

# (b)
w = [0] * 10
for i in range(len(w) + 1):
    print(w[i])

# (c)
a = [1, 2, 3]
b = a
b.append(4)
print(a)

# (d)
m = [[0] * 3] * 3
m[0][0] = 7
print(m)

# (e)
def aggiungi(l):
    l.append(1)

def riassegna(l):
    l = [9]

q = []
aggiungi(q)
riassegna(q)
print(q)

# (f)
def h(x=[]):
    x.append(1)
    return x

print(h(), h())

(a) Lista di None

In Python [None] * 10 crea una lista con 10 riferimenti a None, non 10 oggetti Complessa (è l'analogo del new ComplexNumber[10] in Java, le cui celle contengono null). v[0].reale = 0 cerca di assegnare un attributo a None: AttributeError: 'NoneType' object has no attribute 'reale' (in Java: NullPointerException). Gli oggetti vanno creati uno per cella:

python
v = [Complessa() for _ in range(10)]

(b) Un indice di troppo

range(len(w) + 1) produce 0,…,100, \dots, 10 ma gli indici validi sono 0,…,90, \dots, 9: stampa dieci volte 0, poi w[10] solleva IndexError: list index out of range (in Java: ArrayIndexOutOfBoundsException). Il ciclo corretto è range(len(w)). In Python gli indici negativi sono validi (w[-1] è l'ultimo): l'errore compare solo fuori da [−len,len−1][-\text{len}, \text{len}-1].

(c) Alias

b = a non copia la lista: a e b sono due nomi per lo stesso oggetto (b is a è True). b.append(4) modifica l'oggetto, quindi print(a) stampa [1, 2, 3, 4]. Per una copia indipendente: a[:], list(a) o a.copy(); c = a[:] dà un oggetto diverso ma uguale (c is a falso, c == a vero). Per le stringhe, che sono immutabili, il problema non c'è: s[0:len(s)] è uguale a s ma non si può modificare né l'una né l'altra.

(d) Righe condivise

[[0] * 3] * 3 ripete lo stesso riferimento a una sola lista interna tre volte: m[0][0] = 7 modifica quella lista e quindi tutte e tre le righe: stampa [[7, 0, 0], [7, 0, 0], [7, 0, 0]]. Per una matrice di righe indipendenti: [[0] * 3 for _ in range(3)] (stampa [[7, 0, 0], [0, 0, 0], [0, 0, 0]]). Le copie "superficiali" (copy, [:]) duplicano solo il livello esterno: per copiare anche le liste interne serve copy.deepcopy.

(e) Modificare o riassegnare il parametro

aggiungi(q) passa il riferimento alla lista (passaggio per assegnazione): l.append(1) modifica l'oggetto di q. riassegna(q) associa invece il nome locale l a una nuova lista [9]: q non cambia. Stampa [1]. Vale la regola: una funzione può modificare un oggetto mutabile ricevuto, non può far puntare a un altro oggetto il nome del chiamante.

(f) Valore di default mutabile

Il valore di default [] viene creato una sola volta, quando si esegue def, e poi riusato a ogni chiamata senza argomento: h() e h() restituiscono la stessa lista, che dopo le due chiamate vale [1, 1], quindi print(h(), h()) stampa [1, 1] [1, 1]. La forma corretta usa None:

python
def h(x=None):
    if x is None:
        x = []
    x.append(1)
    return x

Verifica

python
v = [None] * 3
try:
    for i in range(3):
        v[i].reale = 0
except AttributeError as e:
    print("AttributeError:", e)

w = [0] * 3
try:
    for i in range(len(w) + 1):
        print(w[i], end=" ")
except IndexError as e:
    print("\nIndexError:", e)
print(w[-1] == w[len(w) - 1])                # True

a = [1, 2, 3]; b = a; c = a[:]
b.append(4)
print(a, b is a, c, c is a, c == [1, 2, 3])   # [1, 2, 3, 4] True [1, 2, 3] False True

m = [[0] * 3] * 3; m[0][0] = 7
print(m)                                      # [[7, 0, 0], [7, 0, 0], [7, 0, 0]]
m = [[0] * 3 for _ in range(3)]; m[0][0] = 7
print(m)                                      # [[7, 0, 0], [0, 0, 0], [0, 0, 0]]

def aggiungi(l): l.append(1)
def riassegna(l): l = [9]
q = []; aggiungi(q); riassegna(q)
print(q)                                      # [1]

def h(x=[]):
    x.append(1)
    return x
print(h(), h())                               # [1, 1] [1, 1]

Errori comuni

  • Credere che [None] * n o [Classe] * n creino n oggetti.
  • range(len(v) + 1) o range(1, len(v)) (salta il primo): errori di uno.
  • Usare b = a per copiare, o [[0] * c] * r per una matrice.
  • Aspettarsi che l = ... dentro la funzione cambi la lista del chiamante.
  • Valori di default mutabili ([], {}).

Versione ripasso

Questionari di novembre e dicembre 2022 e test di gennaio 2014 (UniPD, in Java: NullPointerException, ArrayIndexOutOfBoundsException). Teoria: Variabili, oggetti e riferimentiIn Python una variabile è un nome legato a un oggetto; tipo, identità e valore; assegnamento, aliasing, oggetti mutabili e immutabili, None.Variabili, oggetti e riferimenti →, Liste in Pythonlist come sequenza mutabile (array dinamico); indici, slicing e assegnamento a fette; metodi e loro costo; aliasing, copia superficiale e profonda; list comprehension; liste annidate e matrici.Liste in Python →.

  • [None] * 10 → 10 riferimenti a None: v[i].reale = 0 solleva AttributeError (gli oggetti vanno creati: [Complessa() for _ in range(10)]).
  • range(len(w) + 1) → IndexError all'ultima iterazione (indici validi −len,…,len−1-\text{len}, \dots, \text{len}-1).
  • b = a è un alias (b is a): b.append(4) cambia anche a. Copia: a[:], list(a), a.copy() (superficiale), copy.deepcopy per liste di liste.
  • [[0] * 3] * 3: tre volte la stessa riga; [[0] * 3 for _ in range(3)] per righe indipendenti.
  • Passaggio per assegnazione: l.append(1) modifica l'oggetto del chiamante, l = [9] no.
  • Default mutabile def h(x=[]): creato una volta, condiviso tra le chiamate; si usa x=None.

Errori comuni: [Classe] * n creduto nn oggetti; errori di uno; b = a per copiare; l = ... creduto visibile al chiamante; default mutabili.

Teoria collegata