2008-12-14 7 views
55

A veces parece natural tener un parámetro predeterminado que es una lista vacía. Sin embargo, Python ofrece un comportamiento inesperado en estas situaciones.¿Cuál es la manera pitónica de evitar los parámetros predeterminados que son listas vacías?

Si, por ejemplo, tengo una función:

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

La primera vez que es llamada con el valor por defecto va a funcionar, pero las llamadas después de que se utilice una lista en constante actualización.

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

+10

si alguien está interesado en saber por qué sucede esto, echa un vistazo a http://effbot.org/zone/default-values.htm –

+0

El mismo comportamiento ocurre para los conjuntos , aunque necesitas un ejemplo un poco más complicado para que aparezca como un error. – abeboparebop

Respuesta

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

es cómo lo hago.

+0

¿Es mejor decir: if working_list == Ninguno: o if working_list: ?? –

+0

if not working_list: –

+1

Esta es la forma preferida de hacerlo en python, incluso si no me gusta, ya que es feo. Diría que la mejor práctica sería "si working_list es None". –

11
No

que importa en este caso, pero se puede utilizar la identidad del objeto para probar Ninguno:

if working_list is None: working_list = [] 

También puede tomar ventaja de la forma en que el operador booleano o se define 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 lista_de_trabajo y espera que su función modifique la lista que le dio.

1

Podría estar fuera del tema, pero recuerde que si solo quiere pasar una cantidad variable de argumentos, la manera pitónica es pasar una tupla *args o 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 desea pasar un diccionario:

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

Si la finalidad de la función es a modificar parámetro pasado como working_list , vea la respuesta de HenryR (= Ninguno, verifique que no haya nada dentro).

Pero si no tenía intención de mutar el argumento, sólo lo utilizan como punto de partida para una lista, sólo tiene que copiarlo:

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

(o en este caso sencillo simplemente print starting_list + ["a"] pero supongo eso era solo un ejemplo de juguete)

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

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

  • Si hace esto para construir eficientemente una larga lista de resultados sin construir listas intermedias, considere escribirlo como un generador y usar result_list.extend(myFunc()) cuando lo está llamando.De esta manera, sus convenciones de llamadas permanecen muy limpias.

un patrón en el que la mutación de un arg opcional es hecho con frecuencia es un arg oculto "memo" en las funciones recursivas:

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

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

respuestas existentes ya han proporcionado las soluciones directas como pedido. Sin embargo, dado que este es un escollo 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 se resume muy bien en "Guía de autoestopistas para Python" como "Argumentos predeterminados de mutación":

Cita: ". los argumentos por defecto de Python son evaluados una vez cuando se define la función, no cada vez que se invoca la función (como lo es en, por ejemplo, Ruby) Esto significa que si se utiliza un argumento predeterminado mutable y mutar lo hará, y habrá 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 
0

Ya se han proporcionado respuestas correctas y correctas. Sólo quería dar otra sintaxis para escribir lo que quiere hacer lo que me parece más bello cuando, por ejemplo, desea crear una clase con defecto listas vacías:

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 else if . Me gusta especialmente porque es un pequeño y ordenado delineador de líneas sin dos puntos, etc. involucrado y casi parece una frase en inglés normal. :)

En su caso se podría escribir

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