Un argumento por defecto en Python se evalúa una sola vez, cuando se define la función. Si ese valor por defecto es una lista o un diccionario, todas las llamadas comparten el mismo objeto.
Esta es la sorpresa más reportada de Python, y produce errores que parecen decir que la función recuerda llamadas anteriores.
El error
Tres llamadas, cada una pensada para empezar con una canasta vacía.
# the bug
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item('apple'))
print(add_item('bread'))
print(add_item('milk'))
Imprime:
['apple']
['apple', 'bread']
['apple', 'bread', 'milk']
La canasta sigue creciendo. Nadie se la pasó, y nunca se vació.
Por qué pasa
El valor por defecto se guarda en el objeto función mismo. Puedes mirarlo directamente.
# why it happens
def add_item2(item, basket=[]):
basket.append(item)
return basket
print(add_item2.__defaults__)
add_item2('x')
print(add_item2.__defaults__)
Imprime:
([],)
(['x'],)
__defaults__ guarda la misma lista que la función reparte. Agregar dentro del cuerpo agrega a esa lista guardada, así que la siguiente llamada la ve.
La solución
Usa None como valor por defecto y construye el valor real dentro de la función.
# the fix
def add_item3(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item3('apple'))
print(add_item3('bread'))
Imprime:
['apple']
['bread']
Ahora la lista se crea en cada llamada, porque el cuerpo corre en cada llamada.
Aplica a todo valor por defecto mutable
Las listas, los diccionarios y los conjuntos se comportan así. Los valores por defecto inmutables como números, cadenas, tuplas y fechas son seguros, porque nada los puede cambiar.
# it applies to every mutable default
import datetime
def log(message, seen={}, stamps=[], when=datetime.date(2020, 1, 1)):
seen[message] = True
stamps.append(when)
return len(seen), len(stamps)
print(log('first'))
print(log('second'))
Imprime:
(1, 1)
(2, 2)
El valor por defecto de la fecha está bien. El diccionario y la lista no.
La misma trampa en el cuerpo de una clase
Una lista asignada en el cuerpo de la clase pertenece a la clase, así que todas las instancias la comparten.
# the same trap in a class body
class Basket:
items = [] # shared by every instance
a, b = Basket(), Basket()
a.items.append('apple')
print(b.items, a.items is b.items)
class BasketFixed:
def __init__(self):
self.items = []
c, d = BasketFixed(), BasketFixed()
c.items.append('apple')
print(d.items, c.items is d.items)
Imprime:
['apple'] True
[] False
Asigna los atributos mutables en __init__, no en el cuerpo de la clase.
Qué recordar
-
Los argumentos por defecto se evalúan una sola vez, al definir la función.
-
Un valor por defecto mutable lo comparten todas las llamadas que no pasen uno.
-
Usa
Nonecomo valor por defecto y crea el valor dentro de la función. -
La misma regla aplica a los atributos mutables en el cuerpo de una clase.
Las dataclasses lanzan un ValueError si intentas esto, que es uno de los pocos lugares donde Python te detiene en vez de dejar que pase en silencio.