python default value parameters




"Almost Astonishment" e l'argomento Mutable Default (20)

Chiunque armeggi 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 molto sorprendente (per un novizio):

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

Un mio manager una volta ha avuto il suo primo incontro con questa funzione e l'ha definito "un difetto di design drammatico" della lingua. Ho risposto che il comportamento aveva una spiegazione sottostante, ed è davvero molto sconcertante e inaspettato se non capisci gli interni. Tuttavia, non ero in grado di rispondere (a me stesso) alla seguente domanda: qual è la ragione per legare l'argomento predefinito alla definizione della funzione e non all'esecuzione della funzione? Dubito che il comportamento esperto abbia un uso pratico (chi ha veramente usato le variabili statiche in C, senza generare bug?)

Modifica :

Baczek ha fatto un esempio interessante. Insieme alla maggior parte dei tuoi commenti e 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]

Per me, sembra che la decisione di progettazione fosse relativa a dove collocare l'ambito dei parametri: all'interno della funzione o "insieme" con esso?

Fare il binding all'interno della funzione significherebbe che x è effettivamente associato al default specificato quando la funzione viene chiamata, non definita, qualcosa che presenterebbe un difetto profondo: la linea def sarebbe "ibrida" nel senso che parte del binding (dell'oggetto funzione) avverrebbe alla definizione e parte (assegnazione dei parametri predefiniti) al momento dell'invocazione della funzione.

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


5 punti in difesa di Python

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

  2. Coerenza : Python passa sempre oggetti, non nomi. Il parametro predefinito è, ovviamente, parte dell'intestazione della funzione (non del corpo della funzione). Dovrebbe quindi essere valutato al momento del caricamento del modulo (e solo al tempo di caricamento del modulo, a meno che non sia annidato), non al momento della chiamata della funzione.

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

  4. Documentazione sufficiente : nella documentazione di base su Python, il tutorial, il problema è annunciato a voce alta come "Avviso importante" nella prima sottosezione della Sezione "Ulteriori informazioni sulla definizione delle funzioni" . L'avvertimento utilizza anche il grassetto, che viene applicato raramente al di fuori delle intestazioni. RTFM: leggi il manuale di precisione.

  5. Meta-apprendimento : cadere nella trappola è in realtà un momento molto utile (almeno se sei uno studente riflessivo), perché in seguito capirai meglio il punto "Consistenza" qui sopra e ciò ti insegnerà molto su Python.


Architettura

L'assegnazione di valori predefiniti in una chiamata di funzione è un odore di codice.

def a(b=[]):
    pass

Questa è una firma di una funzione che non ha funzionato. Non solo per i problemi descritti da altre risposte. Non entrerò in questo qui.

Questa funzione mira a fare due cose. Crea un nuovo elenco ed esegui una funzionalità, molto probabilmente su tale elenco.

Le funzioni che fanno due cose sono cattive funzioni, come apprendiamo dalle pratiche di codice pulito.

Attaccando questo problema con il polimorfismo, estenderemo la lista python o includeremo una in una classe, quindi eseguiremo la nostra funzione su di essa.

Ma aspetta che tu dica, mi piace il mio one-liner.

Bene, indovina un po '. Il codice è più di un semplice modo per controllare il comportamento dell'hardware. È un modo di:

  • comunicare con altri sviluppatori, lavorando sullo stesso codice.

  • essere in grado di cambiare il comportamento dell'hardware quando sorgono nuovi requisiti.

  • essere in grado di comprendere il flusso del programma dopo aver ripreso il codice dopo due anni per apportare la modifica di cui sopra.

Non lasciare le bombe a tempo per te da raccogliere più tardi.

Separando questa funzione nelle due cose che fa, abbiamo bisogno di una classe

class ListNeedsFives(object):
    def __init__(self, b=None):
        if b is None:
            b = []
        self.b = b

    def foo():
        self.b.append(5)

Eseguito da

a = ListNeedsFives()
a.foo()
a.b

E perché è meglio che schiacciare tutto il codice sopra in un'unica funzione.

def dontdothis(b=None):
    if b is None:
        b = []
    b.append(5)
    return b

Perché non farlo?

A meno che tu fallisca nel tuo progetto, il tuo codice vivrà. Molto probabilmente la tua funzione farà più di questo. Il modo corretto di rendere il codice gestibile è quello di separare il codice in parti atomiche con un ambito adeguatamente limitato.

Il costruttore di una classe è un componente molto comunemente riconosciuto da chiunque abbia fatto programmazione orientata agli oggetti. Posizionare la logica che gestisce l'istanza di lista nel costruttore rende il carico cognitivo di comprendere ciò che il codice fa più piccolo.

Il metodo foo()non restituisce l'elenco, perché no?

Nel restituire una lista stand-alone, si può presumere che è sicuro fare ciò che mai ti piace. Ma potrebbe non esserlo, poiché è anche condiviso dall'oggetto a. Costringendo l'utente a fare riferimento ad esso come abricorda dove la lista appartiene. Qualsiasi nuovo codice che desideri modificare absarà naturalmente inserito nella classe, a cui appartiene.

la def dontdothis(b=None):funzione di firma non ha nessuno di questi vantaggi.


Perché non ti introspeti?

Sono davvero sorpreso che nessuno abbia eseguito l'intuizione introspettiva offerta da Python ( 2 e 3 ) su 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 oggetto code per questa funzione. Mentre questo passo di compilazione è fatto, Python valuta * e quindi memorizza gli argomenti predefiniti (una lista vuota [] qui) nell'oggetto funzione stesso . Come la risposta principale menzionata: l'elenco a può ora essere considerato un membro della funzione func .

Quindi, facciamo un'introspezione, una prima e una dopo per esaminare come l'elenco viene espanso all'interno dell'oggetto funzione. Sto usando Python 3.x per questo, per Python 2 lo stesso vale (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 esegue questa definizione, prenderà i parametri predefiniti specificati ( a = [] qui) e li __defaults__ nell'attributo __defaults__ per l'oggetto funzione (sezione rilevante: Callables):

>>> func.__defaults__
([],)

Ok, quindi una lista vuota come singola voce in __defaults__ , proprio come previsto.

Funzione dopo l'esecuzione:

Eseguiamo ora questa funzione:

>>> func()

Ora vediamo nuovamente __defaults__ :

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

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

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

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

La soluzione comune per combatterla è usare None come default e quindi inizializzarsi 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 una nuova lista vuota se nessun argomento è stato passato per a .

Per verificare ulteriormente che l'elenco in __defaults__ sia uguale a quello utilizzato nella funzione func è possibile modificare la propria funzione per restituire l' id della lista utilizzata 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 lista:

>>> 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, provare ad eseguire quanto segue:

def bar(a=input('Did you just see me without calling the function?')): 
    pass  # use raw_input in Py2

come noterete, input() viene chiamato prima che venga eseguito il processo di costruzione della funzione e il suo vincolo alla bar del nome.


AFAICS nessuno ha ancora pubblicato la parte rilevante della documentation :

I 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 è definita, e che lo stesso valore "pre-calcolato" viene utilizzato per ogni chiamata. Questo è particolarmente importante per capire quando un parametro predefinito è un oggetto mutabile, come un elenco o un dizionario: se la funzione modifica l'oggetto (ad esempio aggiungendo un elemento a un elenco), il valore predefinito viene modificato. Questo generalmente non è ciò che era inteso. Un modo per aggirare questo è usare None come predefinito, e testarlo esplicitamente nel corpo della funzione [...]


Ero solito pensare che creare gli oggetti in fase di esecuzione sarebbe stato l'approccio migliore. Ora sono meno sicuro, poiché perdi alcune funzioni utili, anche se potrebbe valerne la pena, a prescindere semplicemente dalla prevenzione della confusione dei newbie. Gli svantaggi di farlo sono:

1. Prestazioni

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

Se viene utilizzata la valutazione del tempo di chiamata, la funzione costosa viene chiamata ogni volta che la funzione viene utilizzata senza argomenti. Pagheresti un prezzo costoso per ogni chiamata o dovrai memorizzare manualmente il valore esternamente, inquinando il tuo spazio dei nomi e aggiungendo verbosità.

2. Forzare i parametri associati

Un trucco utile è quello di associare i parametri di una lambda all'attacco corrente di una variabile quando viene creata la lambda. Per esempio:

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

Questo restituisce un elenco di funzioni che restituiscono rispettivamente 0,1,2,3 .... Se il comportamento viene modificato, si collegheranno invece al valore call-time di i, in modo da ottenere un elenco di funzioni restituite tutte 9 .

L'unico modo per implementarlo diversamente sarebbe creare un'ulteriore chiusura con il limite i, 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 le impostazioni predefinite usando 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 ecc.

Ora, supponiamo che il comportamento dei valori predefiniti possa essere modificato in modo che questo 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 vediamo quali sono gli argomenti predefiniti. Poiché gli oggetti non sono stati costruiti, non possiamo mai impossessarcene senza chiamare effettivamente la funzione. Il meglio che possiamo fare è archiviare il codice sorgente e restituirlo come una stringa.


In realtà, questo non è un difetto di progettazione, e non è dovuto a internals o performance.
Viene semplicemente dal fatto che le funzioni in Python sono oggetti di prima classe e non solo un pezzo di codice.

Non appena riesci a ragionare in questo modo, allora ha perfettamente senso: una funzione è un oggetto che viene valutato sulla sua definizione; i parametri di default 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 buona dei motivi di questo comportamento nei Valori dei parametri di default in Python .
L'ho trovato molto chiaro e suggerisco di leggerlo per una migliore conoscenza di come funzionano gli oggetti funzione.


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

tranne per il caso di chiamare esplicitamente func (None, None), che ignoreremo.

In altre parole, invece di valutare i parametri di default, perché non memorizzarli e valutarli quando viene chiamata la funzione?

Probabilmente una risposta è proprio lì - trasformerebbe efficacemente ogni funzione con i parametri di default in una chiusura. Anche se è tutto nascosto nell'interprete e non in una chiusura completa, i dati devono essere memorizzati da qualche parte. Sarebbe più lento e userà più memoria.


Questo comportamento è facilmente spiegato da:

  1. la dichiarazione di funzione (classe ecc.) viene eseguita una sola volta, creando tutti gli oggetti valore di default
  2. 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
  1. a non cambia - ogni chiamata di assegnazione crea un nuovo oggetto int - viene stampato un nuovo oggetto
  2. b non cambia: il nuovo array viene creato dal valore predefinito e stampato
  3. c cambia - l'operazione viene eseguita sullo stesso oggetto - e viene stampata

È possibile aggirare questo sostituendo l'oggetto (e quindi il legame con l'ambito):

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

Brutto, ma funziona.


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

Ci sono 3 casi che vedo dove potresti incontrare questo problema:

  1. Hai intenzione di modificare l'argomento come un effetto collaterale della funzione. In questo caso non ha mai senso avere un argomento predefinito. L'unica eccezione è quando si sta abusando dell'elenco degli argomenti per avere attributi di funzione, ad esempio cache={}, e non ci si aspetterebbe che chiamino la funzione con un argomento effettivo.
  2. Avete intenzione di lasciare l'argomento non modificato, ma per sbaglio ha fatto modificarlo. È un bug, sistemalo.
  3. Si intende modificare l'argomento da utilizzare all'interno della funzione, ma non si aspettava che la modifica fosse visibile al di fuori della funzione. In tal caso è necessario creare una copia dell'argomento, indipendentemente dal fatto che sia o meno l'impostazione predefinita! Python non è un linguaggio call-by-value, quindi non crea la copia per te, devi essere esplicito a riguardo.

L'esempio nella domanda potrebbe rientrare nella categoria 1 o 3. È strano che entrambi modifichi la lista passata e la restituisca; dovresti scegliere l'uno o l'altro.


Argomento già impegnativo, ma da quello che ho letto qui, il seguente mi ha aiutato a capire come funziona 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

Basta cambiare la funzione per essere:

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

Le soluzioni qui sono:

  1. Utilizzare Nonecome valore predefinito (o un nonce object) e attivarlo per creare i valori in fase di esecuzione; o
  2. Usa un lambdacome parametro predefinito e chiamalo all'interno di un blocco try per ottenere il valore predefinito (questo è il tipo di cosa che l'astrazione lambda è per).

La seconda opzione è piacevole perché gli utenti della funzione possono passare in un callable, che potrebbe essere già esistente (come a type)


Penso che la risposta a questa domanda risieda nel modo in cui Python passa i dati al parametro (passa per valore o per riferimento), non per la mutabilità o come Python gestisce l'istruzione "def".

Una breve introduzione. Innanzitutto, esistono due tipi di dati in python, uno è semplice tipo di dati elementari, come i numeri e un altro tipo di dati è oggetti. In secondo luogo, quando si passano dati ai parametri, python passa il tipo di dati elementare per valore, cioè, crea una copia locale del valore su una variabile locale, ma passa oggetto per riferimento, cioè puntatori all'oggetto.

Ammettendo i due punti precedenti, spieghiamo cosa è successo al codice Python. È solo a causa del passaggio per riferimento per gli oggetti, ma non ha nulla a che fare con mutevole / immutabile, o probabilmente il fatto che l'istruzione "def" viene eseguita solo una volta quando viene definita.

[] è un oggetto, quindi python passa il riferimento di [] a a, cioè, aè solo un puntatore a [] che si trova in memoria come oggetto. C'è solo una copia di [] con, tuttavia, molti riferimenti ad essa. Per il primo pippo (), l'elenco [] viene modificato in 1 con il metodo append. Ma nota che c'è solo una copia dell'oggetto lista e questo oggetto diventa 1 . Quando si esegue il secondo foo (), ciò che dice la pagina web di effbot (gli oggetti non vengono più valutati) è sbagliato. aviene valutato come oggetto della lista, anche se ora il contenuto dell'oggetto è 1 . Questo è l'effetto del passaggio per riferimento! Il risultato di foo (3) può essere facilmente derivato allo stesso modo.

Per convalidare ulteriormente la mia risposta, diamo un'occhiata a due codici aggiuntivi.

====== N. 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, così è None(il primo è mutabile mentre il secondo è immutabile, ma la mutabilità non ha nulla a che fare con la domanda). Nessuno è da qualche parte nello spazio, ma sappiamo che è lì e c'è solo una copia di None lì. Quindi ogni volta che viene invocato foo, gli elementi vengono valutati (al contrario di una risposta che viene valutata solo una volta) per essere Nessuno, per essere chiari, il riferimento (o l'indirizzo) di Nessuno. Quindi nel foo, l'oggetto è cambiato in [], cioè punta a un altro oggetto che ha un indirizzo diverso.

====== N. 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 di foo (1) rende le voci puntate su un oggetto lista [] con un indirizzo, ad esempio 11111111. il contenuto dell'elenco viene modificato in 1 nella funzione foo nel sequel, ma l'indirizzo non viene modificato, tuttavia 11111111 Quindi sta arrivando foo (2, []). Sebbene [] in foo (2, []) abbia lo stesso contenuto del parametro predefinito [] quando si chiama foo (1), il loro indirizzo è diverso! Dato che forniamo esplicitamente il parametro, dobbiamo itemsprendere l'indirizzo di questo nuovo [], diciamo 2222222, e restituirlo dopo aver apportato qualche modifica. Ora viene eseguito foo (3). poiché soloxviene fornito, gli oggetti devono prendere nuovamente il suo valore predefinito. Qual è il valore predefinito? Viene impostato quando si definisce la funzione foo: l'oggetto elenco situato in 11111111. Quindi gli elementi vengono valutati come l'indirizzo 11111111 con un elemento 1. L'elenco situato in 2222222 contiene anche un elemento 2, ma non è indirizzato da elementi Di Più. Di conseguenza, un'appendice di 3 farà items[1,3].

Dalle spiegazioni di cui sopra, possiamo vedere che la pagina web di effbot raccomandata nella risposta accettata non è riuscita a dare una risposta pertinente a questa domanda. Per di più, penso che un punto della pagina web di effbot sia sbagliato. Penso che il codice riguardante UI.Button sia corretto:

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

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

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

Se eseguiremo x[7]()otterremo 7 come previsto e x[9]()ne forniremo 9, un altro valore di i.


Quando facciamo questo:

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

... assegniamo l'argomento aa un elenco senza nome , se il chiamante non supera il valore di a.

Per rendere le cose più semplici per questa discussione, diamo temporaneamente un nome all'elenco senza nome. Che ne dici pavlo?

def foo(a=pavlo):
   ...

In qualsiasi momento, se il chiamante non ci dice cosa asia, riutilizziamo pavlo.

Se pavloè mutabile (modificabile) e foofinisce per modificarlo, un effetto si nota la prossima volta che fooviene chiamato senza specificare a.

Quindi questo è ciò che vedi (Ricorda, pavloè inizializzato su []):

 >>> foo()
 [5]

Ora, pavloè [5].

La chiamata di foo()nuovo modifica di pavlonuovo:

>>> foo()
[5, 5]

Specificare aquando chiamare foo()assicura pavlonon 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]

Questo comportamento non è sorprendente se si prende in considerazione quanto segue:

  1. Il comportamento degli attributi di classe di sola lettura sui tentativi di assegnazione e così via
  2. Le funzioni sono oggetti (spiegati bene nella risposta accettata).

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

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

... tutte le variabili scoperte al di fuori dell'ambito più interno sono di sola lettura ( un tentativo di scrivere su tale variabile creerà semplicemente una nuova variabile locale nello scope più interno, lasciando invariata la variabile esterna con nome identico ).

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

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

Ecco fooun oggetto ed aè un attributo di foo(disponibile all'indirizzo foo.func_defs[0]). Poiché aè una lista, aè mutabile ed è quindi un attributo di lettura-scrittura di foo. Viene inizializzato nella lista vuota come specificato dalla firma quando la funzione viene istanziata ed è disponibile per la lettura e la scrittura purché esista l'oggetto funzione.

Chiamando foosenza sovrascrivere un valore predefinito utilizza il valore predefinito foo.func_defs. In questo caso, foo.func_defs[0]viene utilizzato anell'ambito del codice dell'oggetto della funzione. Modifiche al acambiamento foo.func_defs[0], che è parte foodell'oggetto e permane tra l'esecuzione del codice in foo.

Ora, confrontalo con l'esempio della documentazione sull'emulazione del comportamento degli argomenti predefiniti di altri linguaggi , in modo tale che i valori predefiniti della firma della funzione vengano utilizzati ogni volta che viene eseguita la funzione:

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

Prendendo in considerazione (1) e (2) , si può vedere perché questo compie il comportamento desiderato:

  • Quando l' foooggetto funzione è istanziato, foo.func_defs[0]è impostato su None, un oggetto immutabile.
  • Quando la funzione viene eseguita con i valori predefiniti (senza nessun parametro specificato Lnella chiamata alla funzione), foo.func_defs[0]( None) è disponibile nello scope locale come L.
  • Su L = [], il compito non può avere successo foo.func_defs[0], perché quell'attributo è di sola lettura.
  • Per (1) , una nuova variabile locale denominata anche Lviene creata nell'ambito locale e utilizzata per il resto della chiamata di funzione. foo.func_defs[0]rimane quindi invariato per future invocazioni di foo.

Una soluzione semplice che utilizza 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]

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

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

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

Ti darò 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 che il comportamento esperto abbia un uso pratico (chi ha veramente usato le variabili statiche in C, senza generare bug?)

Come si può vedere, c'è un miglioramento delle prestazioni quando si usano argomenti predefiniti immutabili. Questo può fare la differenza se si tratta di una funzione chiamata di frequente o l'argomento predefinito richiede molto tempo per essere costruito. Inoltre, tieni a mente che Python non è C. In C hai delle costanti che sono praticamente gratis. In Python non hai questo vantaggio.


La risposta più breve sarebbe probabilmente "definizione è esecuzione", quindi l'intera argomentazione non ha senso. Come esempio più forzato, puoi citare questo:

def a(): return []

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

Spero sia sufficiente dimostrare che non eseguire le espressioni di argomento predefinite al momento dell'esecuzione defdell'istruzione non è facile o non ha senso, o entrambi.

Sono d'accordo che è un trucco quando si tenta di utilizzare i costruttori di default, però.


Questo "bug" mi ha dato un sacco di ore di lavoro straordinario! Ma sto iniziando a vedere un potenziale uso di esso (ma mi sarebbe piaciuto che fosse al momento dell'esecuzione, ancora)

Ti darò quello che vedo come un esempio utile.

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




least-astonishment