¿Cuál es la forma pitónica de evitar parámetros predeterminados que son listas vacías?

117

A veces parece natural tener un parámetro predeterminado que es una lista vacía. Sin embargo, Python da un comportamiento inesperado en estas situaciones .

Si por ejemplo, tengo una función:

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

La primera vez que se llama, el valor predeterminado funcionará, pero las llamadas posteriores actualizarán la lista existente (con una "a" en cada llamada) e imprimirán la versión actualizada.

Entonces, ¿cuál es la forma pitónica de obtener el comportamiento que deseo (una lista nueva en cada llamada)?

John Mulder
fuente
17
Si alguien está interesado en saber por qué sucede esto, visite effbot.org/zone/default-values.htm
Ryan Haining
El mismo comportamiento ocurre con los conjuntos, aunque necesita un ejemplo un poco más complicado para que se muestre como un error.
abeboparebop
A medida que los enlaces mueren, permítanme señalar explícitamente que este es el comportamiento deseado. Las variables predeterminadas se evalúan en la definición de la función (que ocurre la primera vez que se llama) y NO cada vez que se llama a la función. En consecuencia, si mutas un argumento predeterminado mutable, cualquier llamada de función posterior solo puede usar el objeto mutado.
Moritz

Respuestas:

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

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

Los documentos dicen que debe usarlo Nonecomo predeterminado y probarlo explícitamente en el cuerpo de la función.

HenryR
fuente
1
¿Es mejor decir: if working_list == None: o if working_list: ??
John Mulder
2
Esta es la forma preferida de hacerlo en Python, incluso si no me gusta porque es feo. Yo diría que la mejor práctica sería "si working_list es None".
e-satis
21
La forma preferida en este ejemplo es decir: si lista_trabajo es Ninguno. La persona que llama podría haber usado un objeto similar a una lista vacía con un anexo personalizado.
tzot
5
Mohit Ranka: tenga en cuenta que not working_list es True si su longitud es 0. Esto conduce a un comportamiento inconsistente: si la función recibe una lista con algún elemento en ella, su llamante tendrá su lista actualizada, y si la lista está vacía, no será tocado.
Vincent
1
@PatrickT La herramienta correcta depende del caso: una función varargs es muy diferente de una que toma un argumento de lista (opcional). Las situaciones en las que tendrías que elegir entre ellas surgen con menos frecuencia de lo que piensas. Varargs es genial cuando cambia el recuento de argumentos, pero se corrige cuando el código está ESCRITO. Como tu ejemplo. Si es variable en tiempo de ejecución, o si desea llamar f()a una lista, tendrá que llamar a f(*l)cuál es bruto. Peor aún, la implementación mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])apestaría con varargs. Mucho mejor si lo es def mate(birds=[], bees=[]):.
FeRD
26

Las respuestas existentes ya han proporcionado las soluciones directas solicitadas. Sin embargo, dado que este es un error muy común para los nuevos programadores de Python, vale la pena agregar la explicación de por qué Python se comporta de esta manera, que está muy bien resumida en " la Guía para autostopistas de Python " como " Argumentos predeterminados mutables ": http: // docs .python-guide.org / es / latest / writing / gotchas /

Cita: " Los argumentos predeterminados de Python se evalúan una vez cuando se define la función, no cada vez que se llama a la función (como en, por ejemplo, Ruby). Esto significa que si usa un argumento predeterminado mutable y lo muta, lo hará y tendrá mutado ese objeto para todas las llamadas futuras a la función también "

Código de muestra para implementarlo:

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

No es que importe en este caso, pero puede usar la identidad del objeto para probar Ninguno:

if working_list is None: working_list = []

También puede aprovechar cómo se define el operador booleano o en python:

working_list = working_list or []

Aunque esto se comportará inesperadamente si la persona que llama le da una lista vacía (que cuenta como falsa) como working_list y espera que su función modifique la lista que le dio.

doblando
fuente
10

Si la intención de la función es modificar el parámetro pasado como working_list, vea la respuesta de HenryR (= Ninguno, marque Ninguno adentro).

Pero si no tenía la intención de mutar el argumento, simplemente utilícelo como punto de partida para una lista, simplemente puede copiarlo:

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

(o en este simple caso, print starting_list + ["a"]pero supongo que fue solo un ejemplo de juguete)

En general, mutar tus argumentos es de mal estilo en Python. Las únicas funciones que se espera que muten un objeto son los métodos del objeto. Es incluso más raro mutar un argumento opcional: ¿es un efecto secundario que ocurre solo en algunas llamadas realmente la mejor interfaz?

  • Si lo hace desde el hábito de C de "argumentos de salida", eso es completamente innecesario: siempre puede devolver múltiples valores como una tupla.

  • Si hace esto para construir de manera eficiente una larga lista de resultados sin crear listas intermedias, considere escribirlo como un generador y usarlo result_list.extend(myFunc())cuando lo llame. De esta manera, sus convenciones de llamadas se mantienen muy limpias.

Un patrón en el que se hace con frecuencia la mutación de un argumento opcional es un argumento "memo" oculto en funciones recursivas:

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)
Beni Cherniavsky-Paskin
fuente
3

Puede que esté fuera de tema, pero recuerde que si solo desea pasar un número variable de argumentos, la forma pitónica es pasar una tupla *argso un diccionario **kargs. Estos son opcionales y son mejores que la sintaxis myFunc([1, 2, 3]).

Si desea pasar 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]

Si quieres pasar un diccionario:

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

Ya se han proporcionado respuestas buenas y correctas. Solo quería dar otra sintaxis para escribir lo que quieres hacer, lo que encuentro más hermoso cuando, por ejemplo, quieres crear una clase con listas vacías predeterminadas:

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

Este fragmento hace uso de la sintaxis del operador if else. Me gusta especialmente porque es una frase sencilla sin dos puntos, etc., y casi se lee como una oración normal en inglés. :)

En tu caso podrías escribir

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

Tomé la clase de extensión UCSC Python for programmer

Lo cual es cierto de: def Fn (data = []):

a) es una buena idea para que sus listas de datos comiencen vacías con cada llamada.

b) es una buena idea para que todas las llamadas a la función que no proporcionen ningún argumento en la llamada obtengan la lista vacía como datos.

c) es una idea razonable siempre que sus datos sean una lista de cadenas.

d) es una mala idea porque el [] predeterminado acumulará datos y el [] predeterminado cambiará con las llamadas posteriores.

Responder:

d) es una mala idea porque el [] predeterminado acumulará datos y el [] predeterminado cambiará con las llamadas posteriores.

Peter Chen
fuente