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()