Perché il “mutevole argomento di default fix” la sintassi è così brutto, chiede python newbie

StackOverflow https://stackoverflow.com/questions/2639915

  •  27-09-2019
  •  | 
  •  

Domanda

Ora seguente la mia serie di "python newbie domande" e basato su un'altra domanda.

Prerogativa

Vai a http://python.net/~goodger/progetti/pycon/2007/idiomatiche/volantino.html#altre-lingue-sono-le variabili e scorrere fino a "Valori di Default".Vi si possono trovare i seguenti:

def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list

def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list

C'è anche un "Avviso importante" a python.org con questo stesso esempio, tho non proprio dicendo che è "meglio".

Un modo per metterlo

Così, la domanda qui è:perché è il "bene" sintassi più di un problema noto brutto come che in un linguaggio di programmazione che promuove "elegante sintassi" e "facile da usare"?

edit:

Un altro modo per metterlo

Non sto chiedendo perché o come accade (grazie Marco per il link).

Mi sto chiedendo perché non c'è alternativa più semplice built-in lingua.

Penso che un modo migliore sarebbe probabilmente essere in grado di fare qualcosa in def di per sé, in cui il nome discorso potrebbe essere collegato a un "locale" o "nuovo" all'interno del def, mutevole oggetto.Qualcosa di simile a:

def better_append(new_item, a_list=immutable([])):
    a_list.append(new_item)
    return a_list

Sono sicuro che qualcuno può venire con una migliore sintassi, ma sto indovinando che ci deve essere una buona spiegazione del perché questo non è stato fatto.

È stato utile?

Soluzione

argomenti predefiniti vengono valutati al momento viene eseguita l'istruzione def, che è il probabilmente l'approccio più ragionevole: spesso è ciò che si vuole. Se non fosse stato il caso, potrebbe causare confusione risultati quando l'ambiente cambia un po '.

Differenziare con un metodo magico local o qualcosa del genere è tutt'altro che ideale. Python cerca di fare le cose piuttosto semplice e non c'è evidente, la sostituzione chiara per il boilerplate corrente che non ricorrere a scherzi con la semantica piuttosto consistenti Python attualmente.

Altri suggerimenti

Questo è chiamato il 'default mutevoli trappola'. Vedere: http://www.ferg.org/projects/python_gotchas.html#contents_item_6

In sostanza, a_list viene inizializzato quando il programma viene prima interpretata, non ogni volta che si chiama la funzione (come ci si potrebbe aspettare da altre lingue). Quindi non stai ricevendo un nuovo elenco ogni volta che si chiama la funzione, ma si sta riutilizzando lo stesso.

Credo che la risposta alla domanda è che se si desidera aggiungere qualcosa a un elenco, basta farlo, non creare una funzione per farlo.

Questa:

>>> my_list = []
>>> my_list.append(1)

è più chiara e facile da leggere rispetto:

>>> my_list = my_append(1)

Nel caso pratico, se avete bisogno di questo tipo di comportamento, si sarebbe probabilmente creare la propria classe che ha metodi per gestire esso la lista interna.

Il caso di una funzione che permette di uso estremamente specifica facoltativamente passare un elenco di modificare, ma genera un nuovo elenco a meno che non espressamente non passa uno in, è sicuramente non vale la pena una sintassi special-case . Scherzi a parte, se si sta facendo un numero di chiamate a questa funzione, perché mai vorresti speciale-caso la prima chiamata nella serie (passando solo un argomento) per distinguerla da ogni altra (che avrà bisogno di due argomenti per essere in grado di mantenere arricchire un elenco esistente) ?! Per esempio, prendere in considerazione qualcosa di simile (assumendo, naturalmente, che betterappend ha fatto qualcosa di utile, perché nel esempio attuale sarebbe folle chiamarlo al posto di un .append diretta -!):

def thecaller(n):
  if fee(0):
    newlist = betterappend(foo())
  else:
    newlist = betterappend(fie())
  for x in range(1, n):
    if fee(x):
      betterappend(foo(), newlist)
    else:
      betterappend(fie(), newlist)

questo è semplicemente folle, e dovrebbe ovviamente essere, invece,

def thecaller(n):
  newlist = []
  for x in range(n):
    if fee(x):
      betterappend(foo(), newlist)
    else:
      betterappend(fie(), newlist)

sempre con due argomenti, evitando la ripetizione, e la costruzione di logica molto più semplice.

L'introduzione di special-case sintassi incoraggia e sostiene il caso d'uso speciale prima lettera maiuscola, e non c'è davvero non molto senso nell'incoraggiare e sostenere questo estremamente peculiare - l'esistente, la sintassi perfettamente regolare è più che bene per il caso d'uso estremamente raro bene usi; -).

Ho modificato questa risposta per includere pensieri da molti commenti pubblicati in questione.

L'esempio che date è imperfetto.Esso modifica l'elenco che si passa come un effetto collaterale.Se si intende la funzione, non avrebbe senso avere un argomento di default.Né avrebbe senso per restituire l'elenco aggiornato.Senza un argomento di default, il problema va via.

Se l'intento era quello di restituire un nuovo elenco, è necessario fare un copia l'ingresso in lista.Python preferisce che le cose siano esplicite, quindi è a voi per fare la copia.

def better_append(new_item, a_list=[]): 
    new_list = list(a_list)
    new_list.append(new_item) 
    return new_list 

Per qualcosa di un po ' diverso, si può fare un generatore che può prendere una lista o un generatore di ingresso:

def generator_append(new_item, a_list=[]):
    for x in a_list:
        yield x
    yield new_item

Penso che si è sotto l'idea sbagliata che Python tratta mutabili e immutabili argomenti di default in modo diverso;semplicemente non è vero.Piuttosto, l'immutabilità dell'argomento ti fa cambiare il tuo codice in un modo sottile per fare la cosa giusta automaticamente.Prendete il vostro esempio e farlo applicare una stringa piuttosto che un elenco:

def string_append(new_item, a_string=''):
    a_string = a_string + new_item
    return a_string

Questo codice non cambia la stringa passata - non può, perché le stringhe sono immutabili.Crea una nuova stringa, e assegna a_string la nuova stringa.L'argomento di default può essere utilizzato più e più volte, perché non cambia, hai fatto una copia di esso all'inizio.

E se non parlavi di liste, ma di AwesomeSets, una classe appena definito? Vorresti definire ".local" in ogni classe

class Foo(object):
    def get(self):
        return Foo()
    local = property(get)

potrebbe funzionare, ma otterrebbe vecchio davvero veloce, molto presto. Ben presto, il "se a è None: a = CorrectObject ()". Modello diventa una seconda natura, e non troverete brutto - lo troverete illuminante

Il problema non è di sintassi, ma una semantica - i valori dei parametri di default vengono valutati in fase di definizione di funzione, non in fase di esecuzione della funzione.

Probabilmente non si dovrebbe definire questi due funzioni come il bene e il male. È possibile utilizzare il primo con la lista o dizionari di implementare in atto le modifiche degli oggetti corrispondenti. Questo metodo può dare mal di testa se non si sa come mutabili oggetti lavoro, ma dato conosciuto quello che stai facendo è OK, a mio parere.

In modo da avere due metodi diversi per passare i parametri che forniscono diversi comportamenti. E questo è un bene, non vorrei cambiarlo.

credo che tu stia confondendo la sintassi elegante, con zucchero sintattico. La sintassi pitone comunica entrambi gli approcci chiaramente, succede solo che l'approccio corretto appare meno elegante (in termini di linee di sintassi) rispetto all'approccio errato. Ma dal momento che l'approccio corretto, è bene ... non corretta, la sua eleganza è irrilevante. Per quanto riguarda il motivo per cui una cosa del genere si dimostrano in better_append non è implementata, direi che There should be one-- and preferably only one --obvious way to do it. trionfi guadagni minori in eleganza.

Questo è meglio di good_append (), IMO:

def ok_append(new_item, a_list=None):
    return a_list.append(new_item) if a_list else [ new_item ]

Si potrebbe anche essere molto attenti e controllare che a_list era una lista ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top