Domanda

Chiunque armeggia con Python abbastanza a lungo è stato morso (o fatto a pezzi) dal seguente problema:

def foo(a=[]):
    a.append(5)
    return a

I principianti di Python si aspetterebbero che questa funzione restituisca sempre una lista con un solo elemento: [5].Il risultato è invece molto diverso, e davvero sorprendente (per un principiante):

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

Uno dei miei manager una volta ha avuto il suo primo incontro con questa funzionalità 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 se ne comprendono i meccanismi interni.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 sperimentato abbia un uso pratico (chi ha davvero utilizzato variabili statiche in C, senza generare bug?)

Modificare:

Baczek ha fatto un esempio interessante.Insieme alla maggior parte dei tuoi commenti e a quelli di Utaal in particolare, ho approfondito 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 fosse relativa a dove collocare l'ambito dei parametri:all'interno della funzione o "insieme" ad essa?

Fare l'associazione all'interno della funzione significherebbe questo x è effettivamente vincolato al default specificato quando la funzione viene chiamata, non definita, cosa che presenterebbe un profondo difetto:IL def La linea sarebbe "ibrida" nel senso che parte dell'associazione (dell'oggetto funzione) avverrebbe al momento della definizione e parte (assegnazione dei parametri predefiniti) al momento dell'invocazione della funzione.

Il comportamento reale è più coerente:tutto di quella riga viene valutato quando viene eseguita quella riga, ovvero alla definizione della funzione.

È stato utile?

Soluzione

In realtà, questo non è un difetto di progettazione, ed è non a causa di interni, o le prestazioni.
Viene semplicemente dal fatto che le funzioni in Python sono oggetti di prima classe, e non solo una parte di codice.

Non appena si arriva a pensare in questo modo, allora ha senso completamente: una funzione è un oggetto in corso di valutazione sulla sua definizione; parametri di default sono tipo di "dati utente" e quindi il loro stato possono cambiare da una chiamata all'altra - esattamente come in qualsiasi altro oggetto

.

In ogni caso, Effbot ha una bella spiegazione dei motivi per questo comportamento in valori dei parametri di default in Python .
L'ho trovato molto chiaro, e ho davvero suggerire la lettura di esso per una migliore conoscenza di come funzione di oggetti di lavoro.

Altri suggerimenti

Supponiamo di avere il seguente codice

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

Quando vedo la dichiarazione di eat, la cosa meno sorprendente è pensare che se il primo parametro non viene fornito, sarà uguale alla tupla ("apples", "bananas", "loganberries")

Tuttavia, supposto più avanti nel codice, farò qualcosa del genere

def some_random_function():
    global fruits
    fruits = ("blueberries", "mangos")

quindi se i parametri predefiniti fossero vincolati all'esecuzione della funzione anziché alla dichiarazione della funzione, rimarrei stupito (in modo molto negativo) nello scoprire che i frutti erano stati modificati.Questo sarebbe più sorprendente, secondo me, che scoprire che il tuo foo la funzione sopra stava modificando l'elenco.

Il vero problema risiede nelle variabili mutabili e tutte le lingue hanno questo problema in una certa misura.Ecco una domanda:supponiamo che in Java ho 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 utilizza il valore di StringBuffer key quando è stata inserita nella mappa o memorizza la chiave per riferimento?In ogni caso, qualcuno rimane stupito;o la persona che ha tentato di estrarre l'oggetto dal Map utilizzando un valore identico a quello con cui l'hanno inserito, o la persona che non riesce a recuperare il proprio oggetto anche se la chiave che sta utilizzando è letteralmente lo stesso oggetto utilizzato per inserirlo nella mappa (questo è in realtà perché Python non consente che i suoi tipi di dati incorporati mutabili vengano utilizzati come chiavi del dizionario).

Il tuo esempio è un buon caso in cui i nuovi arrivati ​​​​di Python saranno sorpresi e morsi.Ma direi che se "risolvessimo" questo problema, ciò creerebbe solo una situazione diversa in cui verrebbero invece morsi, e quella sarebbe ancora meno intuitiva.Inoltre, questo è sempre il caso quando si ha a che fare con variabili mutabili;ti imbatti sempre in casi in cui qualcuno potrebbe aspettarsi intuitivamente 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 viene definita e quell'oggetto è sempre quello predefinito.Suppongo che potrebbero utilizzare un caso speciale utilizzando un elenco vuoto, ma quel tipo di involucro speciale causerebbe ancora più stupore, per non parlare del fatto che sarebbe incompatibile con le versioni precedenti.

AFAICS nessuno ha ancora pubblicato la parte relativa alla documentazione :

  

valori dei parametri predefiniti vengono valutati quando viene eseguita la definizione della funzione. Ciò significa che l'espressione viene valutata una volta, quando la funzione viene definita, e che lo stesso valore “pre-calcolate” viene utilizzato per ogni chiamata. Ciò è particolarmente importante capire quando un parametro predefinito è un oggetto mutabile come una lista o un dizionario: se la funzione modifica l'oggetto (ad esempio aggiungendo un elemento a un elenco), il valore di default è in effetti modificato. Questo in genere non è ciò che si intendeva. Un modo per aggirare questo è quello di utilizzare None come predefinito, ed esplicitamente prova per esso nel corpo della funzione [...]

Non so nulla circa il funzionamento interno Python interprete (e io non sono un esperto di compilatori e interpreti o) quindi non prendetevela con me se io propongo qualcosa di unsensible o impossibile.

A condizione che gli oggetti Python sono mutabili Penso che questo dovrebbe essere preso in considerazione quando si progetta la roba argomenti predefiniti. Quando si crea un'istanza un elenco:

a = []

si aspetta di ottenere un nuovo a cui fa riferimento a.

Perché dovrebbe il a=[] in

def x(a=[]):

un'istanza di un nuovo elenco sulla definizione di funzione e non su invocazione? E 'proprio come si sta chiedendo "se l'utente non fornisce l'argomento allora instantiate un nuovo elenco e utilizzarlo come se fosse stata prodotta dal chiamante". Penso che questo sia ambiguo invece:

def x(a=datetime.datetime.now()):

utente, vuoi a per difetto al datetime corrispondente a quando si sta definendo o l'esecuzione di x? In questo caso, come in quello precedente, terrò lo stesso comportamento, come se l'argomento di default "assegnazione" è stata la prima istruzione della funzione (datetime.now() invitato funzione di chiamata). D'altra parte, se l'utente ha voluto la mappatura definizione in tempo avrebbe potuto scrivere:

b = datetime.datetime.now()
def x(a=b):

Lo so, lo so: è una chiusura. In alternativa Python potrebbe fornire una parola chiave per forzare definizione in tempo binding:

def x(static a=b):

Beh, il motivo è semplicemente che attacchi sono fatte quando viene eseguito il codice, e viene eseguita la definizione della funzione, beh ... quando è definito le funzioni.

Confrontare questo:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

Questo codice soffre la stessa casualità inaspettato. banane è un attributo di classe, e, di conseguenza, quando si aggiungono le cose ad esso, viene aggiunto a tutte le istanze di quella classe. La ragione è esattamente la stessa.

E 'solo "come funziona", e farla funzionare in modo diverso nel caso la funzione probabilmente essere complicato, e nel caso della classe probabilmente impossibile, o almeno rallentare istanze di oggetti molto, come si dovrebbe tenere il codice di classe intorno ed eseguirlo quando vengono creati gli oggetti.

Sì, è inaspettato. Ma una volta che cade il centesimo, si adatta perfettamente con il modo in Python funziona in generale. In realtà, è un buon strumento didattico, e una volta capito il motivo per cui questo accade, ti Grok pitone molto meglio.

Che ha detto che dovrebbe un posto di rilievo in ogni buon Python tutorial. Perché, come si parla, tutti corrono in questo problema prima o poi.

Ho usato pensare che la creazione degli oggetti in fase di esecuzione sarebbe l'approccio migliore. Sono meno certo ora, dal momento che non perde alcune funzioni utili, anche se può essere la pena a prescindere semplicemente per evitare newbie confusione. Gli svantaggi di questa operazione sono:

1. Prestazioni

def foo(arg=something_expensive_to_compute())):
    ...

Se si utilizza la valutazione delle chiamate in tempo, allora la funzione costoso viene chiamata ogni volta che la funzione viene utilizzato senza un argomento. Faresti pagare un caro prezzo per ogni chiamata, o hai bisogno di cache manualmente il valore esternamente, inquinando lo spazio dei nomi e l'aggiunta di verbosità.

2. Costringendo i parametri legati

Un trucco utile è quello di legare i parametri di un lambda al corrente legame di una variabile quando viene creata la lambda. Ad esempio:

funcs = [ lambda i=i: i for i in range(10)]

Questo restituisce un elenco di funzioni che restituiscono 0,1,2,3 ... rispettivamente. Se il comportamento è cambiato, saranno invece legano i al call-time valore di i, quindi si dovrebbe ottenere un elenco di funzioni che tutti tornati 9.

L'unico modo per implementare questo altrimenti sarebbe creare un ulteriore chiusura con l'i vincolato, ossia:

def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]

3. Introspezione

Si consideri il codice:

def foo(a='test', b=100, c=[]):
   print a,b,c

Possiamo ottenere informazioni sugli argomenti e le impostazioni predefinite utilizzando il modulo inspect, che

>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))

Questa informazione è molto utile per cose come la generazione di documenti, metaprogrammazione, decoratori etc.

Ora, supponiamo che il comportamento di default potrebbe essere modificato in modo che questo è 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 entrare in possesso di loro senza realmente chiamare la funzione. Il meglio che possiamo fare è quello di memorizzare off il codice sorgente e ritorno che come una stringa.

Perché non fai un'introspezione?

Io sono Veramente sorpreso che nessuno abbia eseguito l'introspezione approfondita offerta da Python (2 E 3 applicare) sui richiamabili.

Data una piccola e semplice funzione func definito come:

>>> def func(a = []):
...    a.append(5)

Quando Python lo incontra, la prima cosa che farà è compilarlo per creare un file code oggetto per questa funzione.Mentre viene eseguita questa fase di compilazione, Pitone valuta* poi I negozi gli argomenti predefiniti (un elenco vuoto [] qui) nell'oggetto funzione stesso.Come menzionato nella risposta principale:la lista a può ora essere considerato a membro della funzione func.

Quindi, facciamo un po' di introspezione, un prima e un dopo per esaminare come l'elenco viene ampliato dentro l'oggetto funzione.sto usando Python 3.x per questo, per Python 2 vale lo stesso (usare __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 stipateli nel __defaults__ attributo per l'oggetto funzione (sezione pertinente:Chiamabili):

>>> func.__defaults__
([],)

Ok, quindi un elenco vuoto come voce unica in __defaults__, proprio come previsto.

Funzione dopo l'esecuzione:

Eseguiamo ora questa funzione:

>>> func()

Ora, vediamoli __defaults__ Ancora:

>>> func.__defaults__
([5],)

Stupito? Il valore all'interno dell'oggetto cambia!Le chiamate consecutive alla funzione ora verranno semplicemente aggiunte a quella incorporata list oggetto:

>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)

Quindi, ecco qua, il motivo per cui questo 'difetto' succede, è perché gli argomenti predefiniti fanno parte dell'oggetto funzione.Non c'è niente di strano qui, è tutto solo un po' sorprendente.

La soluzione comune per combattere questo problema è usare None come predefinito 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, ottieni sempre un nuovo elenco vuoto se non è stato passato alcun argomento a.


Per verificare ulteriormente che l'elenco in __defaults__ è lo stesso utilizzato nella funzione func puoi semplicemente cambiare la tua funzione per restituire il file id della lista a utilizzato 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 dell'elenco:

>>> def func(a = []): 
...     a.append(5)
...     return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True

Il 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 del processo di creazione della funzione e di associazione al nome bar è fatto.

5 punti in difesa di Python

  1. Semplicità : Il comportamento è semplice nel senso seguente: La maggior parte delle persone cadono in questa trappola solo una volta, non è più volte.

  2. Coerenza : Python sempre passa oggetti, non i nomi. Il parametro predefinito è, ovviamente, parte della funzione voce (non il corpo della funzione). Dovrebbe quindi essere valutati con carico modulo tempo (e solo al momento del caricamento del modulo, salvo nested), non alla funzione di chiamata di tempo.

  3. Utilità : Come Frederik Lundh sottolinea nella sua spiegazione di "valori dei parametri predefiniti in Python" , il comportamento attuale può essere molto utile per la programmazione avanzata. (Usare con parsimonia.)

  4. documentazione sufficiente : Nella documentazione di base di Python, il tutorial, il problema è rumorosamente annunciato come un "Avvertenza importante" in prima sottosezione della sezione "più sulla definizione di funzioni" . L'avvertimento utilizza anche grassetto, che raramente viene applicata al di fuori delle voci. RTFM:. Leggere la fine manuale

  5. meta-apprendimento : caduta nella trappola è in realtà una molto momento utile (almeno se sei uno studente riflettente), perché si successivamente capire meglio il punto "Coerenza" di cui sopra e che la volontà insegnare molto su Python.

Questo comportamento si spiega con facile:

  1. funzione (classe ecc) dichiarazione viene eseguito solo una volta, la creazione di tutti gli oggetti di valore predefinito
  2. tutto è passato per riferimento

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a non cambia - ogni chiamata assegnazione crea nuovo oggetto int - nuovo oggetto viene stampato
  2. b non cambia - nuovo array è costruire dal valore di default e stampato
  3. modifiche c - operazione viene eseguita sullo stesso oggetto - ed è stampato

Quello che stai chiedendo è il motivo per cui questo:

def func(a=[], b = 2):
    pass

Non è internamente equivale 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()

salvo il caso di chiamare in modo esplicito func (None, None), che ignoreremo.

In altre parole, invece di valutare parametri predefiniti, perché non memorizzare ciascuno di loro, e li valutare quando viene chiamata la funzione?

Una risposta è probabilmente proprio lì - si sarebbe trasformato in modo efficace ogni funzione con parametri di default in una chiusura. Anche se è tutto nascosto nel interprete e non una chiusura in piena regola, i dati avuto modo di essere memorizzati da qualche parte. Sarebbe più lento e utilizzare più memoria.

1) Il cosiddetto problema del "argomento predefinito Mutevole" è, in generale, un esempio speciale dimostrando che:
"Tutte le funzioni con questo problema soffrono anche di simili problemi effetto collaterale sul parametro attuale ,"
Cioè contro le regole di programmazione funzionale, generalmente indesiderabile e devono essere fissati 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 : copia
Una soluzione assolutamente sicuro è quello di copy o deepcopy l'oggetto di input e poi di fare tutto ciò 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 mutabili incorporati hanno un metodo di copia come some_dict.copy() o some_set.copy() o possono essere copiati facile come somelist[:] o list(some_list). Ogni oggetto può essere copiato anche da copy.copy(any_object) o più approfondita copy.deepcopy() (quest'ultimo utile se l'oggetto mutabile è composto da oggetti mutabili). Alcuni oggetti sono fondamentalmente basate su effetti collaterali come oggetto "file" e non possono essere riprodotti per significato copia. copiare

una simile domanda SO

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 né salvato in qualsiasi pubblici attributo di un istanza restituita da questa funzione. (Supponendo che privato attributi di esempio non devono essere modificate al di fuori di questa classe o sottoclassi per convenzione. _var1 cioè è un attributo privato)

Conclusione:
Parametri di input oggetti non devono essere modificati in posizione (mutato) né non dovrebbero essere binded in un oggetto restituito dalla funzione. (Se Preferiamo programmazione, senza effetti collaterali, che è fortemente raccomandato. Vedi Wiki di "effetto collaterale" (I primi due paragrafi sono relevent in questo contesto.) .)

2)
Solo se è necessario l'effetto collaterale sul parametro attuale, ma non desiderato sul parametro predefinito allora la soluzione utile è def ...(var1=None): if var1 is None: var1 = [] Leggi tutto ..

3) In alcuni casi è il comportamento mutevole parametri di default utile.

Questo ha in realtà nulla a che fare con i valori di default, diverso da quello che spesso viene in su come un comportamento imprevisto quando si scrivono funzioni con valori di default mutevoli.

>>> 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]

Non ci sono valori di default in vista in questo codice, ma si ottiene esattamente lo stesso problema.

Il problema è che foo è Modifica una variabile mutabile passata dal chiamante, quando il chiamante non si aspetta questo. Codice come questo andrebbe bene se la funzione è stata chiamata qualcosa come append_5; poi il chiamante potrebbe essere chiamata la funzione per modificare il valore che passano in, ed il comportamento ci si aspetterebbe. Ma una tale funzione sarebbe molto improbabile che prendere un argomento di default, e probabilmente non sarebbe tornato nella lista (dato che il chiamante ha già un riferimento a tale elenco, quella appena passata in).

Il tuo foo originale, con un argomento di default, non dovrebbe essere la modifica a se sia stata esplicitamente approvata o ha il valore di default. Il codice dovrebbe lasciare argomenti mutabili da solo se non è chiaro dal contesto / nome / documentazione si suppone gli argomenti da modificare. Utilizzando i valori mutevoli passati come argomenti come provvisori locali è una pessima idea, se siamo in Python o no e se ci sono argomenti di default coinvolte o meno.

Se avete bisogno di manipolare in modo distruttivo una temporanea locale nel corso di calcolare qualcosa, ed è necessario per iniziare la manipolazione da un valore di argomento, è necessario effettuare una copia.

topic già occupato, ma da quello che ho letto qui, di seguito mi ha aiutato a rendersi conto di quanto sta funzionando internamente:

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

E 'un'ottimizzazione delle prestazioni. Come risultato di questa funzionalità, quale di queste due chiamate di funzione ne pensi è più veloce?

def print_tuple(some_tuple=(1,2,3)):
    print some_tuple

print_tuple()        #1
print_tuple((1,2,3)) #2

Ti do un suggerimento. Ecco lo smontaggio (vedi 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
  

dubito il comportamento esperto ha un uso pratico (che in realtà utilizzato le variabili statiche in C, senza bug di allevamento?)

Come si può vedere, ci è un miglioramento delle prestazioni quando si usano argomenti predefiniti immutabili. Questo può fare la differenza se si tratta di una chiamata di frequente la funzione o l'argomento di default richiede molto tempo per costruire. Inoltre, tenete a mente che Python non è C. In C si hanno costanti che sono più o meno libera. In Python non si dispone di questo beneficio.

Python: Il Mutevole argomento di default

argomenti predefiniti vengono valutati al momento la funzione viene compilato in un oggetto funzione. Quando viene utilizzato dalla funzione, più volte da tale funzione, sono e rimangono lo stesso oggetto.

Quando sono mutabili, quando mutati (ad esempio, aggiungendo un elemento ad esso) rimangono mutati sulle chiamate consecutivi.

Restano mutati perché sono lo stesso oggetto ogni volta.

codice equivalente:

Poiché la lista è destinata alla funzione quando l'oggetto funzione viene compilato e istanziato, in questo modo:

def foo(mutable_default_argument=[]): # make a list the default argument
    """function that uses a list"""

è quasi esattamente equivalente a questo:

_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

Manifestazione

Ecco una dimostrazione - è possibile verificare che essi sono lo stesso oggetto ogni volta che si fa riferimento da

  • visto che l'elenco è stato creato prima che la funzione è terminata la compilazione di un oggetto funzione,
  • osservando che l'id è lo stesso ogni volta che la lista si fa riferimento,
  • osservando che l'elenco rimane cambiato quando la funzione che utilizza viene chiamato una seconda volta,
  • osservando l'ordine in cui l'uscita è stampato dalla sorgente (che comodamente numerato per voi):

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

e funzionante con 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']

Questo viola il principio di "Least stupore"?

Questo ordine di esecuzione è spesso fonte di confusione per i nuovi utilizzatori di Python. Se si capisce il modello di esecuzione di Python, allora diventa molto atteso.

La solita istruzione per i nuovi utenti di Python:

Ma questo è il motivo per cui la solita istruzioni ai nuovi utenti è quello di creare i loro argomenti di default come questo, invece:

def example_function_2(default_kwarg=None):
    if default_kwarg is None:
        default_kwarg = []

Questo utilizza la None sia come oggetto sentinella a dire la funzione di se o non abbiamo ottenuto un argomento diverso da quello predefinito. Se otteniamo nessun argomento, allora vogliamo davvero di utilizzare un nuovo elenco vuoto, [], come predefinito.

Come la href="https://docs.python.org/tutorial/controlflow.html#default-argument-values" sezione rel="noreferrer"> esercitazione dice:

  

Se non si desidera che il difetto di essere condiviso tra chiamate successive,   è possibile scrivere la funzione in questo modo:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

La risposta più breve sarebbe probabilmente "definizione è l'esecuzione", quindi l'intero argomento non ha senso stretto. Come esempio più artificioso, si può citare questa:

def a(): return []

def b(x=a()):
    print x

Speriamo che sia sufficiente a dimostrare che non esegue le espressioni degli argomenti di default al momento esecuzione dell'istruzione def non è facile o non ha senso, o entrambi.

Sono d'accordo che è un Gotcha quando si tenta di utilizzare costruttori predefiniti, però.

Una semplice soluzione utilizzando Nessuno

>>> 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]

Questo comportamento non sorprende se si prende in considerazione quanto segue:

  1. Il comportamento degli attributi di classe di sola lettura durante i tentativi di assegnazione e quello
  2. Le funzioni sono oggetti (spiegato bene nella risposta accettata).

Il ruolo di (2) è stato ampiamente trattato in questo thread. (1) è probabilmente il fattore che causa stupore, poiché questo comportamento non è "intuitivo" quando si proviene da altre lingue.

(1) è descritto nel Python tutorial sulle lezioni.Nel tentativo di assegnare un valore a un attributo di classe di sola lettura:

... Tutte le variabili trovate al di fuori dell'ambito più interno sono di sola lettura (Un tentativo di scrivere su una tale variabile creerà semplicemente una nuova variabile locale nell'ambito più interno, lasciando invariato la variabile esterna identica).

Guarda indietro all'esempio originale e considera i punti precedenti:

def foo(a=[]):
    a.append(5)
    return a

Qui foo è un oggetto e a è un attributo di foo (disponibile a foo.func_defs[0]).Da a è un elenco, a è mutabile ed è quindi un attributo di lettura-scrittura di foo.Viene inizializzato nell'elenco vuoto come specificato dalla firma quando viene istanziata la funzione ed è disponibile per la lettura e la scrittura finché esiste l'oggetto funzione.

Chiamando foo senza sovrascrivere un valore predefinito utilizza il valore predefinito da foo.func_defs.In questo caso, foo.func_defs[0] è usato per a nell'ambito del codice dell'oggetto funzione.Cambia in a modifica foo.func_defs[0], che fa parte del foo oggetto e persiste tra l'esecuzione del codice in foo.

Ora, confronta questo con l'esempio dalla documentazione in poi emulando il comportamento predefinito degli argomenti di altri linguaggi, in modo tale che le impostazioni predefinite della firma della funzione vengano utilizzate ogni volta che la funzione viene eseguita:

def foo(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

Prendendo (1) E (2) in considerazione, si può capire perché questo realizza il comportamento desiderato:

  • Quando il foo l'oggetto funzione viene istanziato, foo.func_defs[0] è impostato per None, un oggetto immutabile.
  • Quando la funzione viene eseguita con le impostazioni predefinite (senza alcun parametro specificato per L nella chiamata di funzione), foo.func_defs[0] (None) è disponibile nell'ambito locale come L.
  • Su L = [], l'assegnazione non può avere successo foo.func_defs[0], perché l'attributo è di sola lettura.
  • Per (1), denominata anche una nuova variabile locale L viene creato nell'ambito locale e utilizzato per il resto della chiamata di funzione. foo.func_defs[0] rimane quindi invariato per le future invocazioni di foo.

Le soluzioni qui sono:

  1. Usa None come valore di default (o un object nonce), e l'interruttore da quello di creare i valori in fase di esecuzione; o
  2. Utilizzare un lambda come parametro di default, e lo chiamano all'interno di un blocco try per ottenere il valore di default (questo è il genere di cosa che lambda astrazione è per).

La seconda opzione è bello perché utenti della funzione possono passare in un richiamabile, che può essere già esistente (ad esempio un type)

Ho intenzione di dimostrare una struttura alternativa per passare un valore elenco predefinito di una funzione (funziona altrettanto bene con i dizionari).

Come altri hanno ampiamente commentato, il parametro elenchi è associato alla funzione quando è definita rispetto a quando viene eseguita. Perché liste e dizionari sono mutabili, qualsiasi modifica di questo parametro influenzerà le altre chiamate a questa funzione. Come risultato, le successive chiamate alla funzione riceveranno questo elenco condiviso che potrebbe essere stato alterato per eventuali altre chiamate alla funzione. Peggio ancora, due parametri utilizzano parametri condivisi di questa funzione allo stesso tempo, incurante delle modifiche apportate da altri.

metodo sbagliato (forse ...) :

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

È possibile verificare che essi sono uno e lo stesso oggetto utilizzando id:

>>> id(a)
5347866528

>>> id(b)
5347866528

Per di Brett Slatkin "Efficace Python: 59 modi specifici per scrivere meglio Python", Articolo 20: Usare None e docstring per specificare gli argomenti predefiniti dinamici (. P 48)

  

La convenzione per ottenere il risultato desiderato in Python è quello   fornire un valore predefinito di None e per documentare il comportamento effettivo   nella docstring.

Questa implementazione garantisce che ogni chiamata alla funzione sia riceve la lista predefinita oppure l'elenco passato alla funzione.

Metodo preferito :

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]

Ci possono essere casi d'uso legittimi perché il 'metodo sbagliato' per cui il programmatore destinato il parametro elenco predefinito da condividere, ma questo è più probabile che l'eccezione che la regola.

Quando facciamo questo:

def foo(a=[]):
    ...

... si assegna il a argomento di un senza nome , se il chiamante non supera il valore di una.

Per rendere le cose più semplici per questa discussione, diamo temporaneamente dare la lista senza nome un nome. Che ne dite di pavlo?

def foo(a=pavlo):
   ...

In qualsiasi momento, se il chiamante non ci dice che cosa è a, riutilizziamo pavlo.

Se pavlo è mutevole (modificabile), e foo finisce modificandolo, un effetto notiamo la prossima volta foo viene chiamato senza specificare a.

Quindi questo è ciò che si vede (ricordate, pavlo viene inizializzato a []):

 >>> foo()
 [5]

Ora, pavlo è [5].

Calling foo() modifica ancora pavlo ancora:

>>> foo()
[5, 5]

Come specificare al momento della chiamata a foo() assicura pavlo non viene toccato.

>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]

Quindi, pavlo è ancora [5, 5].

>>> foo()
[5, 5, 5]

A volte sfruttare questo comportamento come alternativa al seguente schema:

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

Se singleton è usato solo da use_singleton, mi piace il seguente schema come una sostituzione:

# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
    return singleton.use_me()

Ho usato questo per istanziare le classi client che accedono a risorse esterne, e anche per la creazione di dicts o elenchi per Memoizzazione.

Dato che io non credo che questo modello è ben nota, mi metto un breve commento in guardia contro fraintendimenti futuri.

È possibile ottenere tutto ciò sostituendo l'oggetto (e quindi il legame con la portata):

def foo(a=[]):
    a = list(a)
    a.append(5)
    return a

Ugly, ma funziona.

Può essere vero che:

  1. Qualcuno sta usando ogni caratteristica del linguaggio / libreria, e
  2. Commutazione del comportamento qui sarebbe imprudente, ma

è del tutto coerente premuto per entrambe le caratteristiche di cui sopra e ancora fare un altro punto:

  1. Si tratta di una caratteristica di confusione ed è un peccato in Python.

Le altre risposte, o almeno alcuni di essi o fare punti 1 e 2, ma non 3, o fare il punto 3 e minimizzare i punti 1 e 2. Ma tutti e tre sono vere.

Può essere vero che il passaggio cavalli nel midstream qui sarebbe chiedere per rottura significativo, e che ci potrebbero essere più problemi creati cambiando Python per gestire intuitivamente frammento apertura di Stefano. E se può essere vero che qualcuno che conosceva bene interni Python potrebbe spiegare un campo minato di conseguenze. Tuttavia,

Il comportamento attuale non è Pythonic, e Python è successo perché molto poco circa la lingua viola il principio del minimo stupore ovunque nei pressi questo male. Si tratta di un problema reale, anche se non sarebbe saggio per sradicarla. Si tratta di un difetto di progettazione. Se si capisce la lingua molto meglio, cercando di tracciare il comportamento, posso dire che C ++ fa tutto questo e molto altro ancora; si impara molto navigando, per esempio, gli errori puntatore sottili. Ma questo non è Pythonic: le persone che hanno a cuore Python sufficiente per perseverare di fronte a questo comportamento sono persone che sono attratti dalla lingua perché Python ha molti meno sorprese rispetto altra lingua. Dilettanti e curiosi diventano Pythonisti quando sono stupiti di quanto poco tempo ci vuole per ottenere qualcosa di lavoro - non a causa di un fl disegno - voglio dire, nascosta puzzle di logica - che i tagli contro le intuizioni di programmatori che sono attratte da Python perché Just Works .

Questo non è un difetto di progettazione . Chiunque inciampa questo sta facendo qualcosa di sbagliato.

Ci sono 3 casi Vedo in cui si potrebbe incorrere in questo problema:

  1. si intende modificare l'argomento come un effetto collaterale della funzione. In questo caso non ha senso di avere un argomento di default. L'unica eccezione è quando si sta abusando la lista degli argomenti di avere gli attributi di funzione, per esempio cache={}, e non sarebbe previsto per chiamare la funzione con un argomento reale a tutti.
  2. È intenzione di lasciare l'argomento non modificato, ma per sbaglio ha modificarlo. Questo è un bug, risolvere il problema.
  3. che si intende modificare l'argomento per l'utilizzo all'interno della funzione, ma non si aspettava la modifica di essere al di fuori visualizzabile della funzione. In questo caso è necessario effettuare una copia dell'argomento, se era il default o no! Python non è un linguaggio chiamata per valore in modo che non rende la copia per voi, è necessario essere espliciti su di esso.

L'esempio nella questione potrebbe cadere in categoria 1 o 3. E 'strano che sia modifica l'elenco passato e lo restituisce; si dovrebbe scegliere uno o l'altro.

Questo "bug" mi ha dato un sacco di ore di lavoro straordinario! Ma sto cominciando a vedere un potenziale uso di esso (ma avrei voluto che fosse al tempo di esecuzione, ancora)

Sto per dare quello che vedo come un utile esempio.

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

stampa il seguente

Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way

Basta cambiare la funzione di essere:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a

Penso che la risposta a questa domanda sta nel modo in cui passaggio pitone dati ai parametri (passaggio per valore o per riferimento), non mutevolezza o come gestire il pitone "def" comunicato.

Una breve introduzione. In primo luogo, ci sono due tipi di tipi di dati in Python, uno è semplice tipo di dati semplici, come i numeri, e un altro tipo di dati è oggetti. In secondo luogo, quando si passa i dati di parametri, pitone passare tipo di dati elementare per valore, cioè, effettuare una copia locale del valore di una variabile locale, ma passano oggetto per riferimento, cioè, i puntatori all'oggetto.

Ammettendo questi due punti, cerchiamo di spiegare cosa è successo al codice python. E 'solo a causa del passaggio per riferimento per gli oggetti, ma non ha nulla a che fare con mutevoli / immutabili, o forse il fatto che "def" istruzione viene eseguita solo una volta quando essa è definita.

[] è un oggetto, in modo pitone passare il riferimento di [] per a, cioè a è solo un puntatore su [] che si trova nella memoria, come un oggetto. C'è solo una copia di [] con, tuttavia, molti riferimenti ad esso. Per la prima foo (), la lista [] viene modificato in 1 con il metodo di accodamento. Ma nota che esiste una sola copia dell'oggetto lista e questo oggetto diventa ora 1 . Quando si esegue il secondo foo (), che cosa pagina web effbot dice (articoli non viene analizzato più) è sbagliato. a è valutata essere l'oggetto lista, anche se ora il contenuto dell'oggetto è 1 . Questo è l'effetto del passaggio per riferimento! Il risultato di foo (3) può essere facilmente ricavato nello stesso modo.

Per validare ulteriormente la mia risposta, diamo uno sguardo a due codici aggiuntivi.

====== 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]

[] è un oggetto, in modo da è None (il primo è mutabile mentre il secondo è immutabile. Ma la mutabilità ha nulla a che fare con la questione). Nessuno è da qualche parte nello spazio, ma sappiamo che c'è e non c'è una sola copia di nessuno lì. Così ogni volta che foo viene richiamato, articoli viene valutata (al contrario di qualche risposta che viene valutata solo una volta) di essere None, per essere chiari, il riferimento (o l'indirizzo) di None. Poi nel foo, elemento viene modificato a [], cioè, punta a un altro oggetto che ha un indirizzo diverso.

====== ======= No. 3

def foo(x, items=[]):
    items.append(x)
    return items

foo(1)    # returns [1]
foo(2,[]) # returns [2]
foo(3)    # returns [1,3]

L'invocazione del foo (1) rendono gli elementi indicano un oggetto elenco [] con un indirizzo, per esempio, 11111111. il contenuto della lista viene modificato in 1 nella funzione foo in seguito, ma l'indirizzo non è cambiato, ancora 11111111. Poi foo (2, []) è in arrivo. Anche se il [] a foo (2, []) ha lo stesso contenuto come il parametro di default [] quando si chiama foo (1), il loro indirizzo è diverso! Dal momento che forniamo il parametro in modo esplicito, items deve prendere l'indirizzo di questo nuovo [], dire 2222222, e restituirlo dopo aver apportato qualche cambiamento. Ora foo (3) viene eseguito. dal momento che solo x è previsto, articoli deve prendere ancora una volta il suo valore di default. Qual è il valore di default? Viene impostato quando definisce la funzione foo: l'oggetto elenco situato nella 11111111. Quindi le voci è considerata per essere l'indirizzo 11111111 avente un elemento 1. L'elenco situato 2222222 contiene anche un elemento 2, ma non è puntato da oggetti qualsiasi Di Più. Di conseguenza, un accodamento di 3 farà items [1,3].

Dalle spiegazioni di cui sopra, possiamo vedere che la href="http://effbot.org/zone/default-values.htm" rel="noreferrer"> effbot pagina web

for i in range(10):
    def callback():
        print "clicked button", i
    UI.Button("button %s" % i, callback)

Ogni pulsante può contenere una funzione di richiamata distinto che visualizzerà diverso valore di i. Posso fornire un esempio per mostrare questo:

x=[]
for i in range(10):
    def callback():
        print(i)
    x.append(callback) 

Se eseguiamo x[7]() ci arriveremo 7 come previsto, e x[9]() volontà dà 9, un altro valore di i.

TLDR:. Default Definire in tempo siano coerenti e strettamente più espressivo


Definizione di una funzione interessa due ambiti: il campo di definizione contenente la funzione e la portata esecuzione contenuto da la funzione. Mentre è abbastanza chiaro come blocchi mappa a scopi, la domanda è: dove def <name>(<args=defaults>): appartiene:

...                           # defining scope
def name(parameter=default):  # ???
    ...                       # execution scope

La parte def name deve di valutare nel perimetro che definisce - vogliamo name sia disponibile lì, dopo tutto. Valutare la funzione solo dentro di sé renderebbe inaccessibile.

Dato parameter è un nome costante, siamo in grado di "valutare" allo stesso tempo come def name. Questo ha anche il vantaggio che produce la funzione con una firma conosciuta come name(parameter=...):, invece di un name(...): nudo.

Ora, quando per valutare default?

La coerenza già dice "alla definizione": tutto il resto del def <name>(<args=defaults>): è meglio valutata a definizione pure. Ritardare parti di esso sarebbe la scelta sorprendente.

Le due scelte non sono equivalenti, sia: se default viene valutata in fase di definizione, può ancora influenzare il tempo di esecuzione. Se default viene valutata in fase di esecuzione, non può influenzare il tempo di definizione. Scegliendo "alla definizione" consente esprimendo entrambi i casi, mentre la scelta "alla esecuzione" può esprimere una sola:

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

Ogni altra risposta spiega il motivo per cui questo è in realtà un comportamento bello e desiderato, o perché non si dovrebbe essere in ogni caso bisogno di questo. Il mio è per quelli testardi che vogliono esercitare il loro diritto di piegare il linguaggio alla loro volontà, non il contrario.

Ci sarà "risolvere" questo comportamento con un decoratore che copierà il valore di default al posto di riutilizzare la stessa istanza per ogni argomento posizionale ha lasciato al suo valore di default.

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

Ora diamo ridefinire la nostra funzione che utilizza questo decoratore:

@sanify
def foo(a=[]):
    a.append(5)
    return a

foo() # '[5]'
foo() # '[5]' -- as desired

Questo è particolarmente utile per le funzioni che accettano più argomenti. Confronto:

# 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

con

# the nasty decorator hack
@sanify
def bar(a=[], b=[], c=[]):
    # wow, works right out of the box!

E 'importante notare che le interruzioni di soluzione di cui sopra, se si tenta di utilizzare args di parole chiave, in questo modo:

foo(a=[4])

Il decoratore potrebbe essere regolata per consentire per questo, ma lasciamo questo come un esercizio per il lettore;)

scroll top