2012-05-20 6 views
19

Estoy en el punto de aprender Python donde estoy tratando con the Mutable Default Argument problem.¿Por qué utilizar None soluciona el problema de argumento predeterminado mutable de Python?

def bad_append(new_item, a_list=[]): 
    a_list.append(new_item) 
    return a_list 

def good_append(new_item, a_list=None): 
    if a_list is None: 
     a_list = [] 
    a_list.append(new_item) 
    return a_list 

entiendo que a_list se inicializa solamente cuando se encontró por primera vez la declaración def, y por eso las llamadas posteriores de bad_append utilizan el mismo objeto lista.

Lo que no entiendo es por qué good_append funciona de manera diferente. Parece que a_list sería todavía se inicializará solo una vez; por lo tanto, la declaración if solo sería verdadera en la primera invocación de la función, lo que significa que a_list solo se reiniciaría a [] en la primera invocación, lo que significa que aún acumularía todos los valores new_item anteriores y seguiría fallando.

¿Por qué no? ¿Qué concepto me estoy perdiendo? ¿Cómo se borra a_list cada vez que se ejecuta good_append?

Respuesta

11

El valor por defecto de a_list (o cualquier otro valor por defecto, para el caso) se almacena en el interior de la función una vez que se ha inicializado y por lo tanto puede ser modificado de ninguna manera:

>>> def f(x=[]): return x 
... 
>>> f.func_defaults 
([],) 
>>> f.func_defaults[0] is f() 

Así que el valor en func_defaults es la misma que está tan bien conocida función (en el interior y volvió en mi ejemplo con el fin de acceder a él desde el exterior.

OIA, lo que sucede cuando se llama a f() es una implícita x = f.func_defaults[0]. Si ese objeto se modifica posteriormente, se Mantendré esa modificación.Por el contrario, una asignación dentro de la función obtiene siempre un nuevo []. Cualquier modificación durará hasta que desaparezca la última referencia a esa []; en la siguiente llamada de función, se crea un nuevo [].

IOW nuevamente, no es cierto que [] obtenga el mismo objeto en cada ejecución, pero (en el caso del argumento predeterminado) solo se ejecuta una vez y luego se preserva.

+1

Muchas gracias; la frase "lo que sucede cuando se llama' f() 'es un implícito' x = f.func_defaults [0] '" fue vital para mi comprensión. –

+1

... tanto que estoy cambiando de opinión, una vez más, y marcándolo como la respuesta correcta. –

14

El problema solo existe si el valor predeterminado es mutable, lo que no es None. Lo que se almacena junto con el objeto de función es el valor predeterminado. Cuando se llama a la función, el contexto de la función se inicializa con el valor predeterminado.

a_list = [] 

simplemente asigna un nuevo objeto al nombre a_list en el contexto de la llamada a la función actual. No modifica None de ninguna manera.

+0

Mi impresión es que el modelo mental de asignación y alcance del PO era erróneo. Reescribí la respuesta para aclararlo. – phihag

+0

Mi modelo mental de asignación fue de hecho erróneo; de hecho, incluso ahora que entiendo un poco mejor el problema, todavía puede serlo. Lo que no entendí fue que cuando se hace 'a_list = None' en la definición de la función, la función internamente tiene * otro nombre para el mismo objeto *, y que el nombre visible del parámetro se reasigna a ese objeto al comienzo de cada invocación de la función. –

4

No, en good_inserta_list no se ha iniciado una sola vez.

Cada vez que se llama a la función sin especificar el argumento a_list, se utiliza el valor predeterminado y se usa y se devuelve una nueva instancia de list, la nueva lista no reemplaza el valor predeterminado.

20

Parece que una_lista todavía se inicializa una sola vez

"inicialización" no es algo que sucede a las variables en Python, ya que las variables en Python son sólo nombres. La "inicialización" solo le sucede a los objetos, y se realiza a través del método de clase '__init__.

Cuando escribe a = 0, eso es una tarea. Es decir que "a se referirá al objeto que se describe con la expresión 0". No es inicialización; a puede nombrar cualquier otra cosa de cualquier tipo en cualquier momento posterior, y eso sucede como resultado de asignar algo más al a. La asignación es solo asignación. El primero no es especial.

Cuando escribe def good_append(new_item, a_list=None), eso no es "inicializar" a_list. Está configurando una referencia interna a un objeto, el resultado de evaluar None, de modo que cuando se invoca good_append sin un segundo parámetro, ese objeto se asigna automáticamente al a_list.

significa una_lista sólo reconfiguradas a [] en la primera invocación

No, a_list se establece en [] cualquier momento que a_list es None, para empezar. Es decir, cuando None se pasa explícitamente, o el argumento se omite.

El problema con [] ocurre porque la expresión[] sólo es evaluaron una vez en este contexto. Cuando se compila la función, se evalúa [], se crea un objeto de lista específico, que está vacío para comenzar, y ese objeto se utiliza como el predeterminado.

¿Cómo se borra a_list cada vez que se ejecuta good_append?

No lo es. No necesita ser.

¿Sabes cómo se describe el problema como que está con "argumentos por defecto mutables"?

None no se puede modificar.

El problema se produce cuando modifica el objeto que el parámetro tiene como valor predeterminado.

a_list = [] no modifica el objeto a_list anteriormente mencionado. No puede; los objetos arbitrarios no pueden transformarse mágicamente in situ en listas vacías. a_list = [] significa que "a_list dejará de referirse a lo que se refería anteriormente y comenzará a referirse al []". El objeto anteriormente mencionado no ha cambiado.

Cuando se compila la función, y uno de los argumentos tiene un valor predeterminado, ese valor - un objeto - se cuela en la función (¡que a su vez es un objeto!). Cuando escribe código que muta un objeto, el objeto muta. Si el objeto al que se hace referencia pasa a ser el objeto cocido en la función, sigue mutando.

Pero no puede mutar None. Es inmutable

Puede mutar []. Es una lista y las listas son mutables. Agregar un elemento a una lista muta la lista.

+0

Gracias por la gran respuesta. Me estoy matando tratando de decidir si marcar esta respuesta o @ glglgl's como correcta. La otra respuesta contiene la única frase iluminadora que me hizo capaz de comprender tu respuesta; su respuesta como un todo es más completa y comprensible, pero de alguna manera no hizo que la luz haga clic en el mismo camino. Si hubiera una manera de dar dos marcas verdes a una pregunta, la suya sería la otra (y podría volver a ser la única si sigo gimiendo). –

Cuestiones relacionadas