Juan Mulder
A veces parece natural tener un parámetro predeterminado que es una lista vacía. Sin embargo, Python produce un comportamiento inesperado en estas situaciones.
Por ejemplo, considere esta 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"
cada convocatoria) e imprima la versión actualizada.
¿Cómo puedo arreglar la función para que, si se llama repetidamente sin un argumento explícito, se use una nueva lista vacía cada vez?
EnriqueR
def my_func(working_list=None):
if working_list is None:
working_list = []
# alternative:
# working_list = [] if working_list is None else working_list
working_list.append("a")
print(working_list)
los documentos di que deberías usar None
como predeterminado y pruébelo explícitamente en el cuerpo de la función.
-
¿Es mejor decir: if lista_de_trabajo == Ninguno: o si lista_de_trabajo: ??
– John Mulder
14 de diciembre de 2008 a las 11:31
-
Esta es la forma preferida de hacerlo en python, incluso si no me gusta porque es feo. Diría que la mejor práctica sería “si working_list es Ninguno”.
– e-satis
14 de diciembre de 2008 a las 12:35
-
La forma preferida en este ejemplo es decir: if working_list is None . La persona que llama podría haber usado un objeto similar a una lista vacía con un anexo personalizado.
– tzot
14 de diciembre de 2008 a las 12:45
-
Mohit Ranka: tenga en cuenta que not working_list es True si su longitud es 0. Esto conduce a un comportamiento inconsistente: si las funciones reciben una lista con algún elemento, la persona que llama tendrá su lista actualizada, y si la lista está vacía, no será tocado.
– Vicente
14 de diciembre de 2008 a las 13:21
-
@PatrickT La herramienta adecuada depende del caso: una función varargs es muy diferente de uno 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 excelente cuando cambia el conteo de argumentos, pero se corrige cuando el código está ESCRITO. Como tu ejemplo. Si es una variable de tiempo de ejecución, o si desea llamar
f()
en una lista, tendrías que llamarf(*l)
que es asqueroso. Peor aún, implementarmate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])
chuparía con varargs. mucho mejor si esdef mate(birds=[], bees=[]):
.– FeRD
11 de junio de 2020 a las 16:56
Zhenhua
Otras respuestas ya han proporcionado las soluciones directas solicitadas, 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 La guía del autoestopista de Python bajo Argumentos predeterminados mutables:
Se evalúan los argumentos predeterminados de Python 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, voluntad y también he mutado ese objeto para todas las futuras llamadas a la función.
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á de manera inesperada 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.
-
El
or
sugerencia se ve bien, pero se comporta sorprendentemente cuando se suministra con0
contra1
oTrue
contraFalse
.– Nico Schlömer
19 de febrero de 2021 a las 8:47
Beni Cherniavsky-Paskin
Si la intención de la función es modificar el parámetro pasado como working_list
vea la respuesta de HenryR (=Ninguno, verifique que no haya 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 caso simple solo print starting_list + ["a"]
pero supongo que fue solo un ejemplo de juguete)
En general, mutar tus argumentos es un mal estilo en Python. Las únicas funciones que se espera que transformen un objeto son los métodos del objeto. Es aún más raro mutar un argumento opcional: ¿un efecto secundario que ocurre solo en algunas llamadas es realmente la mejor interfaz?
-
Si lo hace por 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 eficientemente una larga lista de resultados sin construir listas intermedias, considere escribirlo como un generador y usar
result_list.extend(myFunc())
cuando lo estás llamando. De esta manera, sus convenciones de llamadas permanecen muy limpias.
Un patrón donde mutar un argumento opcional es hecho con frecuencia 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)
Mapad
Podría estar fuera de tema, pero recuerde que si solo desea pasar un número variable de argumentos, la forma 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 quieres 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}
normando
Cita de https://docs.python.org/3/reference/compound_stmts.html#function-definitions
Los valores predeterminados de los parámetros se evalúan de izquierda a derecha cuando se ejecuta la definición de la función. Esto significa que la expresión se evalúa una vez, cuando se define la función, y que se utiliza el mismo valor “precalculado” para cada llamada. Es especialmente importante comprender esto cuando un parámetro predeterminado es un objeto mutable, como una lista o un diccionario: si la función modifica el objeto (por ejemplo, agregando un elemento a una lista), el valor predeterminado se modifica. Esto generalmente no es lo que se pretendía. Una forma de evitar esto es usar Ninguno como valor predeterminado y probarlo explícitamente en el cuerpo de la función, por ejemplo:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
24b4Jeff
Quizás lo más simple de todo es simplemente crear una copia de la lista o tupla dentro del script. Esto evita la necesidad de realizar comprobaciones. Por ejemplo,
def my_funct(params, lst = []):
liste = lst.copy()
. .
-
Esto duplica efectivamente la respuesta de Beni Cherniavsky-Paskin pero con mucho menos detalle.
– Karl Knechtel
ayer
El mismo comportamiento ocurre con los conjuntos, aunque necesita un ejemplo un poco más complicado para que aparezca como un error.
– abeboparebop
19 de noviembre de 2015 a las 12:28
Como 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 muta un argumento predeterminado mutable, cualquier llamada de función posterior solo puede usar el objeto mutado.
– Moritz
3 de julio de 2018 a las 16:50