Qual è il modo pitonico per evitare i parametri predefiniti che sono elenchi vuoti?


117

A volte sembra naturale avere un parametro predefinito che è un elenco vuoto. Eppure Python dà un comportamento inaspettato in queste situazioni .

Se ad esempio ho una funzione:

def my_func(working_list = []):
    working_list.append("a")
    print(working_list)

La prima volta che viene chiamata l'impostazione predefinita funzionerà, ma le chiamate successive aggiorneranno l'elenco esistente (con una "a" ogni chiamata) e stamperanno la versione aggiornata.

Allora, qual è il modo pitonico per ottenere il comportamento che desidero (un elenco nuovo ad ogni chiamata)?


17
se qualcuno è interessato al motivo per cui ciò accade, controlla effbot.org/zone/default-values.htm
Ryan Haining

Lo stesso comportamento si verifica per i set, sebbene sia necessario un esempio leggermente più complicato perché si presenti come bug.
abeboparebop

Quando i collegamenti muoiono, lasciatemi sottolineare esplicitamente che questo è un comportamento desiderato. Le variabili predefinite vengono valutate alla definizione della funzione (che avviene la prima volta che viene chiamata) e NON ogni volta che viene chiamata la funzione. Di conseguenza, se si modifica un argomento predefinito modificabile, qualsiasi chiamata di funzione successiva può utilizzare solo l'oggetto mutato.
Moritz

Risposte:


150
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

    working_list.append("a")
    print(working_list)

I documenti dicono che dovresti usarlo Nonecome predefinito e testarlo esplicitamente nel corpo della funzione.


1
È meglio dire: if working_list == None: o if working_list: ??
John Mulder

2
Questo è il modo preferito per farlo in Python, anche se non mi piace perché è brutto. Direi che la migliore pratica sarebbe "se working_list è None".
e-satis

21
Il modo preferito in questo esempio è dire: se working_list è None. Il chiamante potrebbe aver utilizzato un oggetto simile a un elenco vuoto con un'appendice personalizzata.
tzot

5
Mohit Ranka: attenzione che not working_list è True se la sua lunghezza è 0. Ciò porta a un comportamento incoerente: se la funzione riceve una lista con qualche elemento al suo interno, il suo chiamante avrà la sua lista aggiornata, e se la lista è vuota, non sarà toccato.
vincent

1
@PatrickT Lo strumento giusto dipende dal caso: una funzione varargs è molto diversa da quella che accetta un argomento di lista (opzionale). Le situazioni in cui dovresti scegliere tra di loro si verificano meno spesso di quanto pensi. Varargs è ottimo quando il conteggio degli argomenti cambia, ma viene corretto quando il codice è SCRITTO. Come il tuo esempio. Se si tratta di una variabile di runtime o se si desidera chiamare f()su un elenco, è necessario chiamare f(*l)che è grossolano. Peggio ancora, l'implementazione mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])sarebbe SUCK con varargs. Molto meglio se lo è def mate(birds=[], bees=[]):.
FeRD

26

Le risposte esistenti hanno già fornito le soluzioni dirette richieste. Tuttavia, poiché questa è una trappola molto comune per i nuovi programmatori Python, vale la pena aggiungere la spiegazione del perché Python si comporta in questo modo, che è ben riassunta nella " Guida per autostoppisti a Python " come " Argomenti predefiniti modificabili ": http: // docs .python-guide.org / it / ultima / scrittura / trucchi /

Quote: " Gli argomenti predefiniti di Python vengono valutati una volta quando la funzione è definita, non ogni volta che la funzione viene chiamata (come è in, ad esempio, Ruby). Ciò significa che se usi un argomento predefinito mutabile e lo muti, avrai e avrai ha mutato quell'oggetto anche per tutte le future chiamate alla funzione "

Codice di esempio per implementarlo:

def foo(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

13

Non che sia importante in questo caso, ma puoi usare l'identità dell'oggetto per testare Nessuno:

if working_list is None: working_list = []

Puoi anche sfruttare il modo in cui l'operatore booleano o è definito in python:

working_list = working_list or []

Anche se questo si comporterà in modo imprevisto se il chiamante ti dà un elenco vuoto (che conta come falso) come working_list e si aspetta che la tua funzione modifichi l'elenco che gli ha dato.


10

Se lo scopo della funzione è modificare il parametro passato come working_list, vedere la risposta di HenryR (= Nessuno, controllare Nessuno all'interno).

Ma se non intendevi modificare l'argomento, usalo come punto di partenza per un elenco, puoi semplicemente copiarlo:

def myFunc(starting_list = []):
    starting_list = list(starting_list)
    starting_list.append("a")
    print starting_list

(o in questo semplice caso, print starting_list + ["a"]ma immagino che fosse solo un esempio di giocattolo)

In generale, cambiare i tuoi argomenti è un cattivo stile in Python. Le uniche funzioni che ci si aspetta completamente che mutino un oggetto sono i metodi dell'oggetto. È ancora più raro modificare un argomento opzionale: un effetto collaterale che si verifica solo in alcune chiamate è davvero la migliore interfaccia?

  • Se lo fai dall'abitudine C degli "argomenti di output", non è completamente necessario: puoi sempre restituire più valori come una tupla.

  • Se lo fai per costruire in modo efficiente un lungo elenco di risultati senza costruire elenchi intermedi, considera di scriverlo come generatore e di usarlo result_list.extend(myFunc())quando lo chiami. In questo modo le tue convenzioni di chiamata rimangono molto pulite.

Un modello in cui la modifica di un argomento facoltativo viene eseguita frequentemente è un argomento "memo" nascosto nelle funzioni ricorsive:

def depth_first_walk_graph(graph, node, _visited=None):
    if _visited is None:
        _visited = set()  # create memo once in top-level call

    if node in _visited:
        return
    _visited.add(node)
    for neighbour in graph[node]:
        depth_first_walk_graph(graph, neighbour, _visited)

3

Potrei essere fuori tema, ma ricorda che se vuoi solo passare un numero variabile di argomenti, il modo pitonico è passare una tupla *argso un dizionario **kargs. Questi sono opzionali e sono migliori della sintassi myFunc([1, 2, 3]).

Se vuoi passare una tupla:

def myFunc(arg1, *args):
  print args
  w = []
  w += args
  print w
>>>myFunc(1, 2, 3, 4, 5, 6, 7)
(2, 3, 4, 5, 6, 7)
[2, 3, 4, 5, 6, 7]

Se vuoi passare un dizionario:

def myFunc(arg1, **kargs):
   print kargs
>>>myFunc(1, option1=2, option2=3)
{'option2' : 2, 'option1' : 3}

0

Sono già state fornite risposte valide e corrette. Volevo solo dare un'altra sintassi per scrivere quello che vuoi fare che trovo più bello quando, ad esempio, vuoi creare una classe con elenchi vuoti predefiniti:

class Node(object):
    def __init__(self, _id, val, parents=None, children=None):
        self.id = _id
        self.val = val
        self.parents = parents if parents is not None else []
        self.children = children if children is not None else []

Questo frammento fa uso della sintassi dell'operatore if else. Mi piace soprattutto perché è una piccola riga pulita senza due punti, ecc. E si legge quasi come una normale frase inglese. :)

Nel tuo caso potresti scrivere

def myFunc(working_list=None):
    working_list = [] if working_list is None else working_list
    working_list.append("a")
    print working_list

-3

Ho seguito il corso di estensione UCSC Python for programmer

Il che è vero per: def Fn (data = []):

a) è una buona idea in modo che gli elenchi di dati inizino vuoti ad ogni chiamata.

b) è una buona idea in modo che tutte le chiamate alla funzione che non forniscono alcun argomento sulla chiamata ottengano l'elenco vuoto come dati.

c) è un'idea ragionevole purché i tuoi dati siano un elenco di stringhe.

d) è una cattiva idea perché il valore predefinito [] accumulerà dati e il valore predefinito [] cambierà con le chiamate successive.

Risposta:

d) è una cattiva idea perché il valore predefinito [] accumulerà dati e il valore predefinito [] cambierà con le chiamate successive.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.