2012-01-11 10 views
13
>>> rows = [['']*5]*5 
>>> rows 
[['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']] 
>>> rows[0][0] = 'x' 

Naturalmente, espero filas para convertirse en:¿Por qué usar el operador de multiplicación en la lista crea una lista de punteros?

[['x', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']] 

En su lugar, me sale:

[['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', '']] 

Parece que los elementos de la lista de filas son punteros a la misma edad [ ''] * 5 lista. ¿Por qué funciona de esta manera y es esta una característica de Python?

+0

Como nota al margen, si creo lista por lista de sintaxis de comprensión, obtengo el "que funciona correctamente": 'rows = [['' for x in range (5)] for y in range (5) ] ' – xyzman

+0

Esto también" funciona ":' rows = [[''] * 5 para y en el rango (5)] ' – xyzman

Respuesta

15

El comportamiento no es específico del operador de repetición (*). Por ejemplo, si se concatena dos listas utilizando +, el comportamiento es el mismo:

In [1]: a = [[1]] 

In [2]: b = a + a 

In [3]: b 
Out[3]: [[1], [1]] 

In [4]: b[0][0] = 10 

In [5]: b 
Out[5]: [[10], [10]] 

Esto tiene que ver con el hecho de que las listas son objetos, y los objetos se almacenan por referencia. Cuando usa * y otros, es la referencia que se repite, de ahí el comportamiento que está viendo.

lo que sigue demuestra que todos los elementos de rows tienen la misma identidad (es decir, la dirección de memoria en CPython):

In [6]: rows = [['']*5]*5 

In [7]: for row in rows: 
    ...:  print id(row) 
    ...:  
    ...:  
15975992 
15975992 
15975992 
15975992 
15975992 

El siguiente es equivalente a su ejemplo excepto que crea cinco listas distintas para las filas:

rows = [['']*5 for i in range(5)] 
3

El hecho de que los nombres, parámetros de funciones y contenedores tengan semántica de referencia es una decisión de diseño muy básica en Python. Afecta la forma en que Python trabaja en muchos aspectos, y elegiste solo uno de estos aspectos. En muchos casos, la semántica de referencia es más conveniente, mientras que en otros casos las copias serían más convenientes. En Python, siempre se puede crear de forma explícita una copia si es necesario, o, en este caso, utiliza una lista por comprensión en su lugar:

rows = [[''] * 5 for i in range(5)] 

podría diseñar un lenguaje de programación con una semántica diferente, y hay muchas lenguas que sí tienen semántica diferente, así como también lenguajes con semántica similar. La razón por la que se tomó esta decisión es un poco difícil de responder, un lenguaje solo tiene que tener algo de semántica, y siempre se puede preguntar por qué. Podrías preguntar por qué Python está tipeado dinámicamente, y al final la respuesta es que esto fue Guido decidido en 1989.

3

Tienes razón en que Python está usando punteros "debajo del capó", y sí , esta es una característica. No estoy seguro de por qué lo hicieron de esta manera, supongo que fue por la velocidad y para reducir el uso de memoria.

Este problema es, por cierto, por qué es fundamental entender la distinción entre shallow copies and deep copies.

Cuestiones relacionadas