Es posible que al usar funciones con parámetros por defecto se encuentren con cierto comportamiento inesperado o poco intuitivo de Python. Por estas cosas siempre hay que revisar el código, conocerlo lo mejor posible y saber responder cuando las cosas no funcionan como uno espera.
Veamos el comportamiento de los parametros por defecto en funciones
In [1]:
def funcion(lista=[]):
lista.append(1)
print("La lista vale: {}".format(lista))
Si llamamos a la función una vez...
In [2]:
funcion()
... todo funciona como lo suponemos, pero y si probamos otra vez...
In [3]:
funcion()
funcion()
... ok? No funciona como lo supondriamos.
Esto también podemos extenderlo a clases, donde es comun usar parámetros por defecto:
In [4]:
class Clase:
def __init__(self, lista=[]):
self.lista = lista
self.lista.append(1)
print("Lista de la clase: {}".format(self.lista))
# Instanciamos dos objetos
A = Clase()
B = Clase()
# Modificamos el parametro en una
A.lista.append(5)
# What??
print(A.lista)
print(B.lista)
In [5]:
# Instanciemos algunos objetos
A = Clase()
B = Clase()
C = Clase(lista=["GG"]) # Usaremos esta isntancia como control
print("\nLos objetos son distintos!")
print("id(A): {} \nid(B): {} \nid(C): {}".format(id(A), id(B), id(C)))
print("\nPero la lista es la misma para A y para B :O")
print("id(A.lista): {} \nid(B.lista): {} \nid(C.lista): {}".format(id(A.lista), id(B.lista), id(C.lista)))
In [6]:
# De hecho, tienen atributos...
def funcion(lista=[]):
lista.append(5)
# En la funcion "funcion"...
print("{}".format(funcion.__defaults__))
# ... si la invocamos...
funcion()
# ahora tenemos...
print("{}".format(funcion.__defaults__))
# Si vemos como quedo el metodo "__init__" de la clase Clase...
print("{}".format(Clase.__init__.__defaults__))
El código que define a función es evaluado una vez y dicho valor evaluado es el que se usa en cada llamado posterior. Por lo tanto, al modificar el valor de un parámetro por defecto que es mutable (list
, dict
, etc.) se modifica el valor por defecto para el siguiente llamado.
Una solución simple es usar None
como el valor predeterminado para los parámetros por defecto. Y otra solución es la declaración de variables condicionales:
In [7]:
class Clase:
def __init__(self, lista=None):
# Version "one-liner":
self.lista = lista if lista is not None else list()
# En su version extendida:
if lista is not None:
self.lista = lista
else:
self.lista = list()