"Least Astonishment" e il Mutable Default Argument
Chiunque armeggi con Python abbastanza a lungo è stato morso (o fatto a pezzi) dal seguente problema:
def foo(a=[]):
a.append(5)
return a
Novizi Python si aspetta questa funzione per restituire sempre una lista con un solo elemento: [5]
. Il risultato è invece molto diverso, e molto sorprendente (per un principiante):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
Una volta un mio manager ha avuto il suo primo incontro con questa funzione e l'ha definita "un drammatico difetto di progettazione" del linguaggio. Ho risposto che il comportamento aveva una spiegazione di fondo, ed è davvero molto sconcertante e inaspettato se non si capisce l'interno. Tuttavia, non sono stato in grado di rispondere (a me stesso) alla seguente domanda: qual è il motivo per associare l'argomento predefinito alla definizione della funzione e non all'esecuzione della funzione? Dubito che il comportamento con esperienza abbia un uso pratico (chi ha davvero usato variabili statiche in C, senza allevare bug?)
Modifica :
Baczek ha fatto un esempio interessante. Insieme alla maggior parte dei tuoi commenti e in particolare di Utaal, ho elaborato ulteriormente:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
A me sembra che la decisione progettuale sia stata relativa a dove collocare l'ambito dei parametri: all'interno della funzione o "insieme" ad essa?
Fare l'associazione all'interno della funzione significherebbe che x
è effettivamente vincolata al valore predefinito specificato quando la funzione viene chiamata, non definita, qualcosa che presenterebbe un profondo difetto: la def
linea sarebbe "ibrida" nel senso che la parte dell'associazione (di l'oggetto funzione) avverrebbe al momento della definizione e parte (assegnazione dei parametri predefiniti) al momento della chiamata della funzione.
Il comportamento effettivo è più coerente: tutto di quella riga viene valutato quando viene eseguita quella riga, ovvero alla definizione della funzione.
Risposte
In realtà, questo non è un difetto di progettazione e non è dovuto agli interni o alle prestazioni.
Viene semplicemente dal fatto che le funzioni in Python sono oggetti di prima classe e non solo un pezzo di codice.
Non appena si arriva a pensare in questo modo, allora ha completamente senso: una funzione è un oggetto che viene valutato sulla sua definizione; i parametri predefiniti sono una specie di "dati membro" e quindi il loro stato può cambiare da una chiamata all'altra, esattamente come in qualsiasi altro oggetto.
In ogni caso, Effbot ha una spiegazione molto carina delle ragioni di questo comportamento nei valori dei parametri predefiniti in Python .
L'ho trovato molto chiaro e suggerisco davvero di leggerlo per una migliore conoscenza di come funzionano gli oggetti funzione.
Supponi di avere il codice seguente
fruits = ("apples", "bananas", "loganberries")
def eat(food=fruits):
...
Quando vedo la dichiarazione di mangiare, la cosa meno sorprendente è pensare che se il primo parametro non viene dato, sarà uguale alla tupla ("apples", "bananas", "loganberries")
Tuttavia, supposto più avanti nel codice, faccio qualcosa di simile
def some_random_function():
global fruits
fruits = ("blueberries", "mangos")
quindi se i parametri predefiniti fossero vincolati all'esecuzione della funzione piuttosto che alla dichiarazione della funzione, sarei stupito (in modo molto negativo) di scoprire che i frutti sono stati modificati. Questo sarebbe più sorprendente IMO che scoprire che la tua foo
funzione sopra stava modificando l'elenco.
Il vero problema sta nelle variabili mutabili e tutte le lingue hanno questo problema in una certa misura. Ecco una domanda: supponiamo che in Java io abbia il seguente codice:
StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) ); // does this work?
Ora, la mia mappa usa il valore della StringBuffer
chiave quando è stata inserita nella mappa o la memorizza per riferimento? In ogni caso, qualcuno è stupito; o la persona che ha cercato di estrarre l'oggetto Map
dall'uso di un valore identico a quello con cui lo ha inserito, o la persona che sembra non riuscire a recuperare il proprio oggetto anche se la chiave che stanno usando è letteralmente lo stesso oggetto che è stato utilizzato per metterlo nella mappa (questo è in realtà il motivo per cui Python non consente che i suoi tipi di dati incorporati mutabili vengano usati come chiavi del dizionario).
Il tuo esempio è un buon esempio in cui i nuovi arrivati di Python saranno sorpresi e morsi. Ma direi che se "risolvessimo" questo problema, si creerebbe solo una situazione diversa in cui sarebbero invece stati morsi, e quella sarebbe ancora meno intuitiva. Inoltre, questo è sempre il caso quando si tratta di variabili mutabili; ti imbatti sempre in casi in cui qualcuno potrebbe intuitivamente aspettarsi uno o il comportamento opposto a seconda del codice che sta scrivendo.
Personalmente mi piace l'approccio attuale di Python: gli argomenti della funzione predefinita vengono valutati quando la funzione è definita e quell'oggetto è sempre il valore predefinito. Suppongo che potrebbero essere casi speciali usando un elenco vuoto, ma quel tipo di involucro speciale causerebbe ancora più stupore, per non parlare dell'incompatibilità all'indietro.
La parte rilevante della documentazione :
I valori dei parametri predefiniti vengono valutati da sinistra a destra quando viene eseguita la definizione della funzione. Ciò significa che l'espressione viene valutata una volta, quando la funzione è definita, e che lo stesso valore "pre-calcolato" viene utilizzato per ogni chiamata. Ciò è particolarmente importante per capire quando un parametro predefinito è un oggetto modificabile, come una lista o un dizionario: se la funzione modifica l'oggetto (es. Aggiungendo un elemento a una lista), il valore predefinito viene in effetti modificato. Questo generalmente non è ciò che era previsto. Un modo per aggirare questo è quello di utilizzare
None
come impostazione predefinita e testarlo esplicitamente nel corpo della funzione, ad esempio:def whats_on_the_telly(penguin=None): if penguin is None: penguin = [] penguin.append("property of the zoo") return penguin
Non so nulla del funzionamento interno dell'interprete Python (e non sono nemmeno un esperto di compilatori e interpreti) quindi non incolpare me se propongo qualcosa di insensibile o impossibile.
A condizione che gli oggetti Python siano mutabili, penso che questo dovrebbe essere preso in considerazione quando si progettano gli argomenti predefiniti. Quando crei un'istanza di un elenco:
a = []
ti aspetti di ottenere un nuovo elenco a cui fa riferimento a
.
Perché il file a=[]
in
def x(a=[]):
istanziare un nuovo elenco sulla definizione della funzione e non sull'invocazione? È come se chiedessi "se l'utente non fornisce l'argomento, crea un'istanza di un nuovo elenco e usalo come se fosse stato prodotto dal chiamante". Penso che questo sia invece ambiguo:
def x(a=datetime.datetime.now()):
utente, vuoi impostare a
di default il datetime corrispondente a quando stai definendo o eseguendo x
? In questo caso, come nel precedente, manterrò lo stesso comportamento come se l'argomento predefinito "assegnazione" fosse la prima istruzione della funzione ( datetime.now()
chiamata su chiamata di funzione). D'altra parte, se l'utente volesse la mappatura del tempo di definizione, potrebbe scrivere:
b = datetime.datetime.now()
def x(a=b):
Lo so, lo so: questa è una chiusura. In alternativa Python potrebbe fornire una parola chiave per forzare l'associazione del tempo di definizione:
def x(static a=b):
Bene, la ragione è semplicemente che i collegamenti vengono eseguiti quando viene eseguito il codice e la definizione della funzione viene eseguita, beh ... quando le funzioni sono definite.
Confronta questo:
class BananaBunch:
bananas = []
def addBanana(self, banana):
self.bananas.append(banana)
Questo codice soffre della stessa identica casualità inaspettata. bananas è un attributo di classe e quindi, quando aggiungi cose ad esso, viene aggiunto a tutte le istanze di quella classe. Il motivo è esattamente lo stesso.
È solo "Come funziona", e farlo funzionare in modo diverso nel caso della funzione sarebbe probabilmente complicato, e nel caso della classe probabilmente impossibile, o almeno rallentare molto la creazione di istanze degli oggetti, poiché dovresti mantenere il codice della classe in giro ed eseguirlo quando vengono creati gli oggetti.
Sì, è inaspettato. Ma una volta che il penny scende, si adatta perfettamente a come funziona Python in generale. In effetti, è un buon aiuto per l'insegnamento, e una volta capito il motivo per cui questo accade, ti divertirai molto con Python.
Detto questo, dovrebbe apparire in primo piano in ogni buon tutorial di Python. Perché come dici tu, prima o poi tutti si imbattono in questo problema.
Perché non ti introspetti?
Sono davvero sorpreso che nessuno abbia eseguito la perspicace introspezione offerta da Python ( 2
e 3
applica) sui callables.
Data una semplice piccola funzione func
definita come:
>>> def func(a = []):
... a.append(5)
Quando Python lo incontra, la prima cosa che farà è compilarlo per creare un code
oggetto per questa funzione. Mentre questa fase di compilazione è terminata, Python valuta * e quindi memorizza gli argomenti predefiniti ( []
qui un elenco vuoto ) nell'oggetto funzione stesso . Come indicato nella risposta principale: l'elenco a
può ora essere considerato un membro della funzione func
.
Quindi, facciamo un po 'di introspezione, un prima e un dopo per esaminare come l'elenco viene espanso all'interno dell'oggetto funzione. Sto usando Python 3.x
per questo, per Python 2 vale lo stesso (usa __defaults__
o func_defaults
in Python 2; sì, due nomi per la stessa cosa).
Funzione prima dell'esecuzione:
>>> def func(a = []):
... a.append(5)
...
Dopo che Python ha eseguito questa definizione, prenderà tutti i parametri predefiniti specificati ( a = []
qui) e li inserirà __defaults__nell'attributo per l'oggetto funzione (sezione pertinente: Callables):
>>> func.__defaults__
([],)
Ok, quindi un elenco vuoto come singola voce in __defaults__
, proprio come previsto.
Funzione dopo l'esecuzione:
Eseguiamo ora questa funzione:
>>> func()
Ora, vediamo di __defaults__
nuovo quelli :
>>> func.__defaults__
([5],)
Stupito? Il valore all'interno dell'oggetto cambia! Le chiamate consecutive alla funzione ora verranno semplicemente aggiunte a list
quell'oggetto incorporato :
>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)
Quindi, il gioco è fatto, il motivo per cui si verifica questo "difetto" è perché gli argomenti predefiniti fanno parte dell'oggetto funzione. Non c'è niente di strano qui, è tutto solo un po 'sorprendente.
La soluzione comune per contrastare questo problema è utilizzare None
come impostazione predefinita e quindi inizializzare nel corpo della funzione:
def func(a = None):
# or: a = [] if a is None else a
if a is None:
a = []
Poiché il corpo della funzione viene eseguito di nuovo ogni volta, si ottiene sempre un nuovo elenco vuoto nuovo se non è stato passato alcun argomento a
.
Per verificare ulteriormente che la lista in __defaults__
è la stessa usata nella funzione func
puoi semplicemente cambiare la tua funzione per restituire id
la lista a
usata all'interno del corpo della funzione. Quindi, confrontalo con l'elenco in __defaults__
(posizione [0]
in __defaults__
) e vedrai come questi si riferiscono effettivamente alla stessa istanza di elenco:
>>> def func(a = []):
... a.append(5)
... return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True
Tutto con il potere dell'introspezione!
* Per verificare che Python valuti gli argomenti predefiniti durante la compilazione della funzione, prova a eseguire quanto segue:
def bar(a=input('Did you just see me without calling the function?')):
pass # use raw_input in Py2
come noterai, input()
viene chiamato prima che bar
venga eseguito il processo di creazione della funzione e di associazione al nome .
Pensavo che creare gli oggetti in fase di esecuzione sarebbe stato l'approccio migliore. Ora sono meno sicuro, dal momento che perdi alcune funzioni utili, anche se potrebbe valerne la pena a prescindere semplicemente per evitare confusione tra i principianti. Gli svantaggi di farlo sono:
1. Prestazioni
def foo(arg=something_expensive_to_compute())):
...
Se viene utilizzata la valutazione in fase di chiamata, la funzione costosa viene chiamata ogni volta che la funzione viene utilizzata senza un argomento. Dovresti pagare un prezzo elevato per ogni chiamata o devi memorizzare manualmente il valore esternamente nella cache, inquinando il tuo spazio dei nomi e aggiungendo verbosità.
2. Forzare parametri legati
Un trucco utile consiste nell'associare i parametri di un lambda all'associazione corrente di una variabile quando viene creato il lambda. Per esempio:
funcs = [ lambda i=i: i for i in range(10)]
Restituisce un elenco di funzioni che restituiscono rispettivamente 0,1,2,3 .... Se il comportamento viene modificato, si legheranno invece i
al valore del tempo di chiamata di i, in modo da ottenere un elenco di funzioni restituite tutte 9
.
L'unico modo per implementarlo altrimenti sarebbe creare un'ulteriore chiusura con i bound, ovvero:
def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]
3. Introspezione
Considera il codice:
def foo(a='test', b=100, c=[]):
print a,b,c
Possiamo ottenere informazioni sugli argomenti e sui valori predefiniti utilizzando il inspect
modulo, che
>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))
Queste informazioni sono molto utili per cose come la generazione di documenti, la metaprogrammazione, i decoratori ecc.
Supponiamo ora che il comportamento dei valori predefiniti possa essere modificato in modo che sia l'equivalente di:
_undefined = object() # sentinel value
def foo(a=_undefined, b=_undefined, c=_undefined)
if a is _undefined: a='test'
if b is _undefined: b=100
if c is _undefined: c=[]
Tuttavia, abbiamo perso la capacità di introspezione, e vedere che cosa gli argomenti di default sono . Poiché gli oggetti non sono stati costruiti, non possiamo mai ottenerli senza chiamare effettivamente la funzione. Il meglio che potremmo fare è memorizzare il codice sorgente e restituirlo come stringa.
5 punti in difesa di Python
Semplicità : il comportamento è semplice nel seguente senso: la maggior parte delle persone cade in questa trappola solo una volta, non più volte.
Coerenza : Python passa sempre gli oggetti, non i nomi. Il parametro predefinito è, ovviamente, parte dell'intestazione della funzione (non del corpo della funzione). Pertanto, dovrebbe essere valutato al momento del caricamento del modulo (e solo al momento del caricamento del modulo, a meno che non sia annidato), non al momento della chiamata della funzione.
Utilità : come fa notare Frederik Lundh nella sua spiegazione dei "Valori predefiniti dei parametri in Python" , il comportamento corrente può essere molto utile per la programmazione avanzata. (Usa con parsimonia).
Documentazione sufficiente : nella documentazione di base di Python, il tutorial, il problema è annunciato a gran voce come un "Avviso importante" nella prima sottosezione della sezione "Ulteriori informazioni sulla definizione delle funzioni" . L'avviso utilizza anche il grassetto, che viene applicato raramente al di fuori dei titoli. RTFM: leggi l'ottimo manuale.
Meta-apprendimento : cadere nella trappola è in realtà un momento molto utile (almeno se sei uno studente riflessivo), perché successivamente capirai meglio il punto "Consistenza" sopra e questo ti insegnerà molto su Python.
Questo comportamento è facilmente spiegato da:
- La dichiarazione della funzione (classe ecc.) viene eseguita una sola volta, creando tutti gli oggetti con valore predefinito
- tutto è passato per riferimento
Così:
def x(a=0, b=[], c=[], d=0):
a = a + 1
b = b + [1]
c.append(1)
print a, b, c
a
non cambia - ogni chiamata di assegnazione crea un nuovo oggetto int - il nuovo oggetto viene stampatob
non cambia: il nuovo array viene creato dal valore predefinito e stampatoc
modifiche - l'operazione viene eseguita sullo stesso oggetto - e viene stampata
Quello che stai chiedendo è perché questo:
def func(a=[], b = 2):
pass
non è internamente equivalente a questo:
def func(a=None, b = None):
a_default = lambda: []
b_default = lambda: 2
def actual_func(a=None, b=None):
if a is None: a = a_default()
if b is None: b = b_default()
return actual_func
func = func()
eccetto per il caso di chiamata esplicita di func (None, None), che ignoreremo.
In altre parole, invece di valutare i parametri predefiniti, perché non memorizzare ciascuno di essi e valutarli quando viene chiamata la funzione?
Una risposta è probabilmente proprio lì: trasformerebbe effettivamente ogni funzione con parametri predefiniti in una chiusura. Anche se è tutto nascosto nell'interprete e non una chiusura in piena regola, i dati devono essere archiviati da qualche parte. Sarebbe più lento e userebbe più memoria.
1) Il cosiddetto problema di "Mutable Default Argument" è in generale un esempio speciale che dimostra che:
"Tutte le funzioni con questo problema soffrono anche di un problema di effetto collaterale simile sul parametro attuale ",
Ciò è contro le regole della programmazione funzionale di solito indesiderabile e dovrebbero essere risolti entrambi insieme.
Esempio:
def foo(a=[]): # the same problematic function
a.append(5)
return a
>>> somevar = [1, 2] # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5] # usually expected [1, 2]
Soluzione : una copia
Una soluzione assolutamente sicura è prima copy
o deepcopy
l'oggetto di input e poi fare qualsiasi cosa con la copia.
def foo(a=[]):
a = a[:] # a copy
a.append(5)
return a # or everything safe by one line: "return a + [5]"
Molti tipi modificabili incorporati hanno un metodo di copia come some_dict.copy()
o some_set.copy()
o possono essere copiati facilmente come somelist[:]
o list(some_list)
. Ogni oggetto può anche essere copiato da copy.copy(any_object)
o più approfonditamente da copy.deepcopy()
(quest'ultimo utile se l'oggetto mutabile è composto da oggetti mutabili). Alcuni oggetti sono fondamentalmente basati su effetti collaterali come l'oggetto "file" e non possono essere riprodotti in modo significativo dalla copia. copia
Problema di esempio per una domanda SO simile
class Test(object): # the original problematic class
def __init__(self, var1=[]):
self._var1 = var1
somevar = [1, 2] # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar # [1, 2, [1]] but usually expected [1, 2]
print t2._var1 # [1, 2, [1]] but usually expected [1, 2]
Non dovrebbe essere salvato in alcun attributo pubblico di un'istanza restituita da questa funzione. (Supponendo che gli attributi privati dell'istanza non debbano essere modificati dall'esterno di questa classe o sottoclassi per convenzione. Cioè _var1
è un attributo privato)
Conclusion:
Input parameters objects shouldn't be modified in place (mutated) nor they should not be binded into an object returned by the function. (If we prefere programming without side effects which is strongly recommended. see Wiki about "side effect" (The first two paragraphs are relevent in this context.) .)
2)
Only if the side effect on the actual parameter is required but unwanted on the default parameter then the useful solution is def ...(var1=None):
if var1 is None:
var1 = []
More..
3) In some cases is the mutable behavior of default parameters useful.
This actually has nothing to do with default values, other than that it often comes up as an unexpected behaviour when you write functions with mutable default values.
>>> def foo(a):
a.append(5)
print a
>>> a = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]
No default values in sight in this code, but you get exactly the same problem.
The problem is that foo
is modifying a mutable variable passed in from the caller, when the caller doesn't expect this. Code like this would be fine if the function was called something like append_5
; then the caller would be calling the function in order to modify the value they pass in, and the behaviour would be expected. But such a function would be very unlikely to take a default argument, and probably wouldn't return the list (since the caller already has a reference to that list; the one it just passed in).
Your original foo
, with a default argument, shouldn't be modifying a
whether it was explicitly passed in or got the default value. Your code should leave mutable arguments alone unless it is clear from the context/name/documentation that the arguments are supposed to be modified. Using mutable values passed in as arguments as local temporaries is an extremely bad idea, whether we're in Python or not and whether there are default arguments involved or not.
If you need to destructively manipulate a local temporary in the course of computing something, and you need to start your manipulation from an argument value, you need to make a copy.
Already busy topic, but from what I read here, the following helped me realizing how it's working internally:
def bar(a=[]):
print id(a)
a = a + [1]
print id(a)
return a
>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object
[1]
>>> id(bar.func_defaults[0])
4484370232
It's a performance optimization. As a result of this functionality, which of these two function calls do you think is faster?
def print_tuple(some_tuple=(1,2,3)):
print some_tuple
print_tuple() #1
print_tuple((1,2,3)) #2
I'll give you a hint. Here's the disassembly (see http://docs.python.org/library/dis.html):
#
1
0 LOAD_GLOBAL 0 (print_tuple)
3 CALL_FUNCTION 0
6 POP_TOP
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
#
2
0 LOAD_GLOBAL 0 (print_tuple)
3 LOAD_CONST 4 ((1, 2, 3))
6 CALL_FUNCTION 1
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
I doubt the experienced behavior has a practical use (who really used static variables in C, without breeding bugs ?)
As you can see, there is a performance benefit when using immutable default arguments. This can make a difference if it's a frequently called function or the default argument takes a long time to construct. Also, bear in mind that Python isn't C. In C you have constants that are pretty much free. In Python you don't have this benefit.
Python: The Mutable Default Argument
Default arguments get evaluated at the time the function is compiled into a function object. When used by the function, multiple times by that function, they are and remain the same object.
When they are mutable, when mutated (for example, by adding an element to it) they remain mutated on consecutive calls.
They stay mutated because they are the same object each time.
Equivalent code:
Since the list is bound to the function when the function object is compiled and instantiated, this:
def foo(mutable_default_argument=[]): # make a list the default argument
"""function that uses a list"""
is almost exactly equivalent to this:
_a_list = [] # create a list in the globals
def foo(mutable_default_argument=_a_list): # make it the default argument
"""function that uses a list"""
del _a_list # remove globals name binding
Demonstration
Here's a demonstration - you can verify that they are the same object each time they are referenced by
- seeing that the list is created before the function has finished compiling to a function object,
- observing that the id is the same each time the list is referenced,
- observing that the list stays changed when the function that uses it is called a second time,
- observing the order in which the output is printed from the source (which I conveniently numbered for you):
example.py
print('1. Global scope being evaluated')
def create_list():
'''noisily create a list for usage as a kwarg'''
l = []
print('3. list being created and returned, id: ' + str(id(l)))
return l
print('2. example_function about to be compiled to an object')
def example_function(default_kwarg1=create_list()):
print('appending "a" in default default_kwarg1')
default_kwarg1.append("a")
print('list with id: ' + str(id(default_kwarg1)) +
' - is now: ' + repr(default_kwarg1))
print('4. example_function compiled: ' + repr(example_function))
if __name__ == '__main__':
print('5. calling example_function twice!:')
example_function()
example_function()
and running it with python example.py
:
1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']
Does this violate the principle of "Least Astonishment"?
This order of execution is frequently confusing to new users of Python. If you understand the Python execution model, then it becomes quite expected.
The usual instruction to new Python users:
But this is why the usual instruction to new users is to create their default arguments like this instead:
def example_function_2(default_kwarg=None):
if default_kwarg is None:
default_kwarg = []
This uses the None singleton as a sentinel object to tell the function whether or not we've gotten an argument other than the default. If we get no argument, then we actually want to use a new empty list, []
, as the default.
As the tutorial section on control flow says:
If you don’t want the default to be shared between subsequent calls, you can write the function like this instead:
def f(a, L=None): if L is None: L = [] L.append(a) return L
The shortest answer would probably be "definition is execution", therefore the whole argument makes no strict sense. As a more contrived example, you may cite this:
def a(): return []
def b(x=a()):
print x
Hopefully it's enough to show that not executing the default argument expressions at the execution time of the def
statement isn't easy or doesn't make sense, or both.
I agree it's a gotcha when you try to use default constructors, though.
This behavior is not surprising if you take the following into consideration:
- The behavior of read-only class attributes upon assignment attempts, and that
- Functions are objects (explained well in the accepted answer).
The role of (2) has been covered extensively in this thread. (1) is likely the astonishment causing factor, as this behavior is not "intuitive" when coming from other languages.
(1) is described in the Python tutorial on classes. In an attempt to assign a value to a read-only class attribute:
...all variables found outside of the innermost scope are read-only (an attempt to write to such a variable will simply create a new local variable in the innermost scope, leaving the identically named outer variable unchanged).
Look back to the original example and consider the above points:
def foo(a=[]):
a.append(5)
return a
Here foo
is an object and a
is an attribute of foo
(available at foo.func_defs[0]
). Since a
is a list, a
is mutable and is thus a read-write attribute of foo
. It is initialized to the empty list as specified by the signature when the function is instantiated, and is available for reading and writing as long as the function object exists.
Calling foo
without overriding a default uses that default's value from foo.func_defs
. In this case, foo.func_defs[0]
is used for a
within function object's code scope. Changes to a
change foo.func_defs[0]
, which is part of the foo
object and persists between execution of the code in foo
.
Now, compare this to the example from the documentation on emulating the default argument behavior of other languages, such that the function signature defaults are used every time the function is executed:
def foo(a, L=None):
if L is None:
L = []
L.append(a)
return L
Taking (1) and (2) into account, one can see why this accomplishes the desired behavior:
- When the
foo
function object is instantiated,foo.func_defs[0]
is set toNone
, an immutable object. - When the function is executed with defaults (with no parameter specified for
L
in the function call),foo.func_defs[0]
(None
) is available in the local scope asL
. - Upon
L = []
, the assignment cannot succeed atfoo.func_defs[0]
, because that attribute is read-only. - Per (1), a new local variable also named
L
is created in the local scope and used for the remainder of the function call.foo.func_defs[0]
thus remains unchanged for future invocations offoo
.
A simple workaround using None
>>> def bar(b, data=None):
... data = data or []
... data.append(b)
... return data
...
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3, [34])
[34, 3]
>>> bar(3, [34])
[34, 3]
I am going to demonstrate an alternative structure to pass a default list value to a function (it works equally well with dictionaries).
As others have extensively commented, the list parameter is bound to the function when it is defined as opposed to when it is executed. Because lists and dictionaries are mutable, any alteration to this parameter will affect other calls to this function. As a result, subsequent calls to the function will receive this shared list which may have been altered by any other calls to the function. Worse yet, two parameters are using this function's shared parameter at the same time oblivious to the changes made by the other.
Wrong Method (probably...):
def foo(list_arg=[5]):
return list_arg
a = foo()
a.append(6)
>>> a
[5, 6]
b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]
# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()
7
You can verify that they are one and the same object by using id
:
>>> id(a)
5347866528
>>> id(b)
5347866528
Per Brett Slatkin's "Effective Python: 59 Specific Ways to Write Better Python", Item 20: Use None
and Docstrings to specify dynamic default arguments (p. 48)
The convention for achieving the desired result in Python is to provide a default value of
None
and to document the actual behaviour in the docstring.
This implementation ensures that each call to the function either receives the default list or else the list passed to the function.
Preferred Method:
def foo(list_arg=None):
"""
:param list_arg: A list of input values.
If none provided, used a list with a default value of 5.
"""
if not list_arg:
list_arg = [5]
return list_arg
a = foo()
a.append(6)
>>> a
[5, 6]
b = foo()
b.append(7)
>>> b
[5, 7]
c = foo([10])
c.append(11)
>>> c
[10, 11]
There may be legitimate use cases for the 'Wrong Method' whereby the programmer intended the default list parameter to be shared, but this is more likely the exception than the rule.
The solutions here are:
- Use
None
as your default value (or a nonceobject
), and switch on that to create your values at runtime; or - Use a
lambda
as your default parameter, and call it within a try block to get the default value (this is the sort of thing that lambda abstraction is for).
The second option is nice because users of the function can pass in a callable, which may be already existing (such as a type
)
You can get round this by replacing the object (and therefore the tie with the scope):
def foo(a=[]):
a = list(a)
a.append(5)
return a
Ugly, but it works.
When we do this:
def foo(a=[]):
...
... we assign the argument a
to an unnamed list, if the caller does not pass the value of a.
To make things simpler for this discussion, let's temporarily give the unnamed list a name. How about pavlo
?
def foo(a=pavlo):
...
At any time, if the caller doesn't tell us what a
is, we reuse pavlo
.
If pavlo
is mutable (modifiable), and foo
ends up modifying it, an effect we notice the next time foo
is called without specifying a
.
So this is what you see (Remember, pavlo
is initialized to []):
>>> foo()
[5]
Now, pavlo
is [5].
Calling foo()
again modifies pavlo
again:
>>> foo()
[5, 5]
Specifying a
when calling foo()
ensures pavlo
is not touched.
>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]
So, pavlo
is still [5, 5]
.
>>> foo()
[5, 5, 5]
I sometimes exploit this behavior as an alternative to the following pattern:
singleton = None
def use_singleton():
global singleton
if singleton is None:
singleton = _make_singleton()
return singleton.use_me()
If singleton
is only used by use_singleton
, I like the following pattern as a replacement:
# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
return singleton.use_me()
I've used this for instantiating client classes that access external resources, and also for creating dicts or lists for memoization.
Since I don't think this pattern is well known, I do put a short comment in to guard against future misunderstandings.
It may be true that:
- Someone is using every language/library feature, and
- Switching the behavior here would be ill-advised, but
it is entirely consistent to hold to both of the features above and still make another point:
- It is a confusing feature and it is unfortunate in Python.
The other answers, or at least some of them either make points 1 and 2 but not 3, or make point 3 and downplay points 1 and 2. But all three are true.
It may be true that switching horses in midstream here would be asking for significant breakage, and that there could be more problems created by changing Python to intuitively handle Stefano's opening snippet. And it may be true that someone who knew Python internals well could explain a minefield of consequences. However,
The existing behavior is not Pythonic, and Python is successful because very little about the language violates the principle of least astonishment anywhere near this badly. It is a real problem, whether or not it would be wise to uproot it. It is a design flaw. If you understand the language much better by trying to trace out the behavior, I can say that C++ does all of this and more; you learn a lot by navigating, for instance, subtle pointer errors. But this is not Pythonic: people who care about Python enough to persevere in the face of this behavior are people who are drawn to the language because Python has far fewer surprises than other language. Dabblers and the curious become Pythonistas when they are astonished at how little time it takes to get something working--not because of a design fl--I mean, hidden logic puzzle--that cuts against the intuitions of programmers who are drawn to Python because it Just Works.
This is not a design flaw. Anyone who trips over this is doing something wrong.
There are 3 cases I see where you might run into this problem:
- You intend to modify the argument as a side effect of the function. In this case it never makes sense to have a default argument. The only exception is when you're abusing the argument list to have function attributes, e.g.
cache={}
, and you wouldn't be expected to call the function with an actual argument at all. - You intend to leave the argument unmodified, but you accidentally did modify it. That's a bug, fix it.
- You intend to modify the argument for use inside the function, but didn't expect the modification to be viewable outside of the function. In that case you need to make a copy of the argument, whether it was the default or not! Python is not a call-by-value language so it doesn't make the copy for you, you need to be explicit about it.
The example in the question could fall into category 1 or 3. It's odd that it both modifies the passed list and returns it; you should pick one or the other.
This "bug" gave me a lot of overtime work hours! But I'm beginning to see a potential use of it (but I would have liked it to be at the execution time, still)
I'm gonna give you what I see as a useful example.
def example(errors=[]):
# statements
# Something went wrong
mistake = True
if mistake:
tryToFixIt(errors)
# Didn't work.. let's try again
tryToFixItAnotherway(errors)
# This time it worked
return errors
def tryToFixIt(err):
err.append('Attempt to fix it')
def tryToFixItAnotherway(err):
err.append('Attempt to fix it by another way')
def main():
for item in range(2):
errors = example()
print '\n'.join(errors)
main()
prints the following
Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way
Just change the function to be:
def notastonishinganymore(a = []):
'''The name is just a joke :)'''
a = a[:]
a.append(5)
return a
I think the answer to this question lies in how python pass data to parameter (pass by value or by reference), not mutability or how python handle the "def" statement.
A brief introduction. First, there are two type of data types in python, one is simple elementary data type, like numbers, and another data type is objects. Second, when passing data to parameters, python pass elementary data type by value, i.e., make a local copy of the value to a local variable, but pass object by reference, i.e., pointers to the object.
Admitting the above two points, let's explain what happened to the python code. It's only because of passing by reference for objects, but has nothing to do with mutable/immutable, or arguably the fact that "def" statement is executed only once when it is defined.
[] is an object, so python pass the reference of [] to a
, i.e., a
is only a pointer to [] which lies in memory as an object. There is only one copy of [] with, however, many references to it. For the first foo(), the list [] is changed to 1 by append method. But Note that there is only one copy of the list object and this object now becomes 1. When running the second foo(), what effbot webpage says (items is not evaluated any more) is wrong. a
is evaluated to be the list object, although now the content of the object is 1. This is the effect of passing by reference! The result of foo(3) can be easily derived in the same way.
To further validate my answer, let's take a look at two additional codes.
====== No. 2 ========
def foo(x, items=None):
if items is None:
items = []
items.append(x)
return items
foo(1) #return [1]
foo(2) #return [2]
foo(3) #return [3]
[]
is an object, so is None
(the former is mutable while the latter is immutable. But the mutability has nothing to do with the question). None is somewhere in the space but we know it's there and there is only one copy of None there. So every time foo is invoked, items is evaluated (as opposed to some answer that it is only evaluated once) to be None, to be clear, the reference (or the address) of None. Then in the foo, item is changed to [], i.e., points to another object which has a different address.
====== No. 3 =======
def foo(x, items=[]):
items.append(x)
return items
foo(1) # returns [1]
foo(2,[]) # returns [2]
foo(3) # returns [1,3]
The invocation of foo(1) make items point to a list object [] with an address, say, 11111111. the content of the list is changed to 1 in the foo function in the sequel, but the address is not changed, still 11111111. Then foo(2,[]) is coming. Although the [] in foo(2,[]) has the same content as the default parameter [] when calling foo(1), their address are different! Since we provide the parameter explicitly, items
has to take the address of this new []
, say 2222222, and return it after making some change. Now foo(3) is executed. since only x
is provided, items has to take its default value again. What's the default value? It is set when defining the foo function: the list object located in 11111111. So the items is evaluated to be the address 11111111 having an element 1. The list located at 2222222 also contains one element 2, but it is not pointed by items any more. Consequently, An append of 3 will make items
[1,3].
From the above explanations, we can see that the effbot webpage recommended in the accepted answer failed to give a relevant answer to this question. What is more, I think a point in the effbot webpage is wrong. I think the code regarding the UI.Button is correct:
for i in range(10):
def callback():
print "clicked button", i
UI.Button("button %s" % i, callback)
Each button can hold a distinct callback function which will display different value of i
. I can provide an example to show this:
x=[]
for i in range(10):
def callback():
print(i)
x.append(callback)
If we execute x[7]()
we'll get 7 as expected, and x[9]()
will gives 9, another value of i
.
TLDR: Define-time defaults are consistent and strictly more expressive.
Defining a function affects two scopes: the defining scope containing the function, and the execution scope contained by the function. While it is pretty clear how blocks map to scopes, the question is where def <name>(<args=defaults>):
belongs to:
... # defining scope
def name(parameter=default): # ???
... # execution scope
The def name
part must evaluate in the defining scope - we want name
to be available there, after all. Evaluating the function only inside itself would make it inaccessible.
Since parameter
is a constant name, we can "evaluate" it at the same time as def name
. This also has the advantage it produces the function with a known signature as name(parameter=...):
, instead of a bare name(...):
.
Now, when to evaluate default
?
Consistency already says "at definition": everything else of def <name>(<args=defaults>):
is best evaluated at definition as well. Delaying parts of it would be the astonishing choice.
The two choices are not equivalent, either: If default
is evaluated at definition time, it can still affect execution time. If default
is evaluated at execution time, it cannot affect definition time. Choosing "at definition" allows expressing both cases, while choosing "at execution" can express only one:
def name(parameter=defined): # set default at definition time
...
def name(parameter=default): # delay default until execution time
parameter = default if parameter is None else parameter
...
Every other answer explains why this is actually a nice and desired behavior, or why you shouldn't be needing this anyway. Mine is for those stubborn ones who want to exercise their right to bend the language to their will, not the other way around.
We will "fix" this behavior with a decorator that will copy the default value instead of reusing the same instance for each positional argument left at its default value.
import inspect
from copy import copy
def sanify(function):
def wrapper(*a, **kw):
# store the default values
defaults = inspect.getargspec(function).defaults # for python2
# construct a new argument list
new_args = []
for i, arg in enumerate(defaults):
# allow passing positional arguments
if i in range(len(a)):
new_args.append(a[i])
else:
# copy the value
new_args.append(copy(arg))
return function(*new_args, **kw)
return wrapper
Now let's redefine our function using this decorator:
@sanify
def foo(a=[]):
a.append(5)
return a
foo() # '[5]'
foo() # '[5]' -- as desired
This is particularly neat for functions that take multiple arguments. Compare:
# the 'correct' approach
def bar(a=None, b=None, c=None):
if a is None:
a = []
if b is None:
b = []
if c is None:
c = []
# finally do the actual work
with
# the nasty decorator hack
@sanify
def bar(a=[], b=[], c=[]):
# wow, works right out of the box!
It's important to note that the above solution breaks if you try to use keyword args, like so:
foo(a=[4])
The decorator could be adjusted to allow for that, but we leave this as an exercise for the reader ;)