Sé que los argumentos predeterminados se crean en el momento de inicialización de la función y no cada vez que se llama a la función. Ver el siguiente código:
def ook (item, lst=[]):
lst.append(item)
print 'ook', lst
def eek (item, lst=None):
if lst is None: lst = []
lst.append(item)
print 'eek', lst
max = 3
for x in xrange(max):
ook(x)
for x in xrange(max):
eek(x)
Lo que no entiendo es por qué esto se implementó de esta manera. ¿Qué beneficios ofrece este comportamiento sobre una inicialización en cada llamada?
Respuestas:
Creo que la razón es la simplicidad de implementación. Déjame elaborar.
El valor predeterminado de la función es una expresión que debe evaluar. En su caso, es una expresión simple que no depende del cierre, pero puede ser algo que contenga variables libres
def ook(item, lst = something.defaultList())
. Si va a diseñar Python, tendrá una opción: ¿lo evalúa una vez cuando se define la función o cada vez que se llama a la función? Python elige el primero (a diferencia de Ruby, que va con la segunda opción).Hay algunos beneficios para esto.
Primero, obtienes un poco de velocidad y aumenta la memoria. En la mayoría de los casos, tendrá argumentos predeterminados inmutables y Python puede construirlos solo una vez, en lugar de en cada llamada a la función. Esto ahorra (algo) de memoria y tiempo. Por supuesto, no funciona bastante bien con valores mutables, pero usted sabe cómo puede moverse.
Otro beneficio es la simplicidad. Es bastante fácil entender cómo se evalúa la expresión: utiliza el alcance léxico cuando se define la función. Si fueran de otra manera, el alcance léxico podría cambiar entre la definición y la invocación y dificultar un poco la depuración. Python hace mucho para ser extremadamente directo en esos casos.
fuente
Una forma de decirlo es que
lst.append(item)
no muta ellst
parámetro.lst
Todavía hace referencia a la misma lista. Es solo que el contenido de esa lista ha sido mutado.Básicamente, Python no tiene (que yo recuerde) ninguna variable constante o inmutable en absoluto, pero tiene algunos tipos constantes e inmutables. No puede modificar un valor entero, solo puede reemplazarlo. Pero puede modificar el contenido de una lista sin reemplazarla.
Al igual que un número entero, no puede modificar una referencia, solo puede reemplazarla. Pero puede modificar el contenido del objeto al que se hace referencia.
En cuanto a la creación del objeto predeterminado una vez, imagino que es principalmente como una optimización, para ahorrar en la creación de objetos y los gastos generales de recolección de basura.
fuente
Le permite seleccionar el comportamiento que desea, como lo demostró en su ejemplo. Entonces, si desea que el argumento predeterminado sea inmutable, utilice un valor inmutable , como
None
o1
. Si desea hacer que el argumento predeterminado sea mutable, use algo mutable, como[]
. Es solo flexibilidad, aunque es cierto, puede morder si no lo sabes.fuente
Creo que la respuesta real es: Python fue escrito como un lenguaje de procedimiento y solo adoptó aspectos funcionales después del hecho. Lo que está buscando es que el valor predeterminado de los parámetros se realice como un cierre, y los cierres en Python están realmente a medias. Para evidencia de este intento:
lo que da
[2, 2, 2]
donde esperarías que se produzca un verdadero cierre[0, 1, 2]
.Hay muchas cosas que me gustaría si Python tuviera la capacidad de ajustar el valor predeterminado de los parámetros en los cierres. Por ejemplo:
Sería extremadamente útil, pero "a" no está en el alcance en el momento de la definición de la función, por lo que no puede hacer eso y en su lugar debe hacer lo torpe:
Lo cual es casi lo mismo ... casi.
fuente
Un gran beneficio es la memorización. Este es un ejemplo estándar:
y para comparar:
Mediciones de tiempo en ipython:
fuente
Ocurre porque la compilación en Python se realiza ejecutando el código descriptivo.
Si uno dijera
sería bastante claro que cada vez querías una nueva matriz.
Pero y si digo:
Aquí supongo que quiero crear cosas en varias listas, y tener un único conjunto global cuando no especifique una lista.
Pero, ¿cómo adivinaría esto el compilador? Entonces, ¿por qué intentarlo? Podríamos confiar en si se nombró o no, y a veces podría ayudar, pero en realidad solo sería adivinar. Al mismo tiempo, hay una buena razón para no intentarlo: consistencia.
Tal como está, Python simplemente ejecuta el código. A la variable list_of_all ya se le asignó un objeto, por lo que ese objeto se pasa por referencia al código que por defecto es x de la misma manera que una llamada a cualquier función obtendría una referencia a un objeto local nombrado aquí.
Si quisiéramos distinguir el caso sin nombre del caso con nombre, eso implicaría que el código en la compilación ejecuta la asignación de una manera significativamente diferente de la que se ejecuta en tiempo de ejecución. Entonces no hacemos el caso especial.
fuente
Esto sucede porque las funciones en Python son objetos de primera clase :
Continúa explicando que la edición del valor del parámetro modifica el valor predeterminado para llamadas posteriores, y que una solución simple de usar None como valor predeterminado, con una prueba explícita en el cuerpo de la función, es todo lo que se necesita para garantizar que no haya sorpresas.
Lo que significa que se
def foo(l=[])
convierte en una instancia de esa función cuando se llama, y se reutiliza para otras llamadas. Piense en los parámetros de la función como algo que se separa de los atributos de un objeto.Los profesionales podrían incluir aprovechar esto para que las clases tengan variables estáticas tipo C. Por lo tanto, es mejor declarar los valores predeterminados Ninguno e inicializarlos según sea necesario:
rendimientos:
en lugar de lo inesperado:
fuente