7.1 Uso errato delle espressioni come valori di default per gli argomenti delle funzioni
Hai già imparato molto, vediamo di analizzare gli errori più comuni dei principianti - è meglio imparare dagli errori degli altri, vero?
Python ti permette di specificare che una funzione può avere argomenti opzionali, assegnando loro un valore di default. Questa è una caratteristica molto comoda, ma può portare a conseguenze spiacevoli se il tipo di tale valore è mutabile. Ad esempio, consideriamo la seguente definizione di funzione:
def foo(bar=[]): # bar - questo è un argomento opzionale
# e di default è una lista vuota.
bar.append("baz") # questa riga può diventare un problema...
return bar
Un errore comune in questo caso è pensare che il valore dell'argomento opzionale venga impostato al valore di default ogni volta che la funzione viene chiamata senza un valore per quell'argomento.
Nel codice sopra, ad esempio, si potrebbe supporre che richiamando di
nuovo la funzione foo()
(cioè senza specificare un valore per
l'argomento bar
), essa ritorni sempre ["baz"]
, poiché si
presume che ogni volta che foo()
viene chiamato (senza
specificare l'argomento bar
), bar
viene impostato su
[]
(cioè una nuova lista vuota).
Ma vediamo cosa accade realmente:
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz", "baz"]
result = foo()
print(result) # ["baz", "baz", "baz"]
Perché la funzione continua ad aggiungere il valore baz
alla
lista esistente ogni volta che viene chiamata
foo()
invece di creare una nuova lista ogni volta?
La risposta sta in una comprensione più approfondita di cosa succede sotto il cofano di Python. In particolare: il valore di default per una funzione viene inizializzato solo una volta, al momento della definizione della funzione.
Pertanto, l'argomento bar
viene inizializzato con il
valore di default (cioè, una lista vuota) solo la prima volta che
foo()
viene definito, mentre le chiamate successive
a foo()
(senza specificare l'argomento bar
)
continueranno a utilizzare la stessa lista che è stata creata per
l'argomento bar
al momento della prima definizione della funzione.
Per riferimento, un comune "workaround" per questo errore è la seguente definizione:
def foo(bar=None):
if bar is None:
bar = []
bar.append("baz")
return bar
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz"]
7.2 Uso errato delle variabili di classe
Consideriamo il seguente esempio:
class A(object):
x = 1
class B(A):
pass
class C(A):
pass
print(A.x, B.x, C.x)
# 1 1 1
Sembra tutto a posto.
Ora assegniamo un valore alla variabile x
della classe B
:
B.x = 2
print(A.x, B.x, C.x)
# 1 2 1
Tutto come previsto.
Adesso cambiamo la variabile di classe A
:
A.x = 3
print(A.x, B.x, C.x)
# 3 2 3
Strano, abbiamo solo modificato A.x
. Perché anche
C.x
è cambiato?
In Python, le variabili di classe sono gestite come dizionari e
seguono ciò che spesso viene chiamato Ordine di Risoluzione dei
Metodi (MRO). Quindi, nel codice sopra, dato che
l'attributo x
non viene trovato nella
classe C
, verrà trovato nelle sue classi
base (solo A
nell'esempio sopra, anche se Python
supporta l'ereditarietà multipla).
In altre parole, C
non ha una propria proprietà
x
, indipendente da A
. Pertanto, i
riferimenti a C.x
sono in realtà riferimenti a
A.x
. Questo può causare problemi se non si gestiscono
correttamente tali casi. Nel studiare Python, fai particolare attenzione
agli attributi di classe e al loro utilizzo.
7.3 Uso errato dei parametri per il blocco di eccezione
Supponiamo che tu abbia il seguente pezzo di codice:
try:
l = ["a", "b"]
int(l[2])
except ValueError, IndexError: # Per catturare entrambe le eccezioni, giusto?
pass
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
IndexError: list index out of range
Il problema qui è che l'espressione except
non accetta un elenco di eccezioni specificato in questo modo. Di conseguenza, nel codice sopra, l'eccezione IndexError
non viene catturata dall'espressione except
; invece, l'eccezione finisce per essere legata a un parametro di nome IndexError
.
Importante!
Questo codice puoi trovarlo in esempi su Internet perché è stato usato in Python 2.x.
Il modo corretto per catturare più eccezioni con un'espressione except
è specificare il primo parametro come una tupla contenente tutte le eccezioni che desideri catturare. Inoltre, per una migliore leggibilità, puoi usare la parola chiave as
, come segue:
try:
l = ["a", "b"]
int(l[2])
except (ValueError, IndexError) as e:
pass
GO TO FULL VERSION