Operador estrella (*) aplicado a listas y enteros

Digamos que tenemos una clase de Foo y queremos instanciar una lista de foos , que podemos hacer de la siguiente manera:

 foo_list = [Foo()] * 3 

Ahora, ¿y si hacemos foo_list[0].x = 5 ? Esta statement establecerá el atributo x en todas las instancias dentro de la lista.

Cuando encontré esto me dejó sin aliento. El problema es que cuando creamos una lista como esa, al parecer Python creará primero la instancia de Foo , luego la instancia de la lista y luego le agregaremos el mismo objeto 3 veces, pero realmente esperaría que hiciera algo como esto:

 foo_list = [Foo() for i in range(3)] 

¿No lo harías tú también? Bueno, ahora sé que definitivamente este azúcar sintáctico no se puede usar como quería usarlo, pero entonces, ¿para qué se usa? Lo único que se me ocurre es crear una lista con un tamaño inicial como este: list = [None] * 5 , y eso no tiene mucho sentido para mí en el caso de python.

¿Esta syntax tiene algún otro caso de uso realmente útil?

Puedes usar la forma de estrella, con cualquier tipo inmutable , como este

 print [5] * 3 print "abc" * 3 print [1.1] * 3 print (8,) * 3 

Digamos, por ejemplo,

 nums = [5] * 3 print map(id, nums) 

Salida en mi máquina

 [41266184, 41266184, 41266184] 

id función id da una identificación única del objeto actual. Como puede ver, crear objetos inmutables de esta manera es muy simple y eficiente. Debido a que todos los elementos en el objeto creado, apuntan al mismo elemento. (Recuerda que los objetos utilizados son inmutables).

Entonces, como regla general ,

Si los objetos son mutables , use la forma de lista de comprensión.

 [Foo() for i in range(3)] 

Si los objetos son inmutables , el uso puede usar la forma de estrella.

 [5] * 3 

No es azúcar sintáctica, es simplemente multiplicar una lista. Es decir,

 foo_list = [Foo()] * 3 

es

 foo_list = ( [Foo()] ) * 3 

es

 foo_instance = Foo() bar_list = [foo_instance] foo_list = bar_list * 3 

No hay syntax especial en absoluto. Crea una lista que tiene el mismo contenido que la lista que se multiplica, tres veces.

Una cosa para la que lo uso es cuando obtengo una lista de longitud desconocida y quiero que tenga la longitud n exacta, luego le agrego una lista de n veces un valor predeterminado y luego la divido:

 def set_to_length(l, n, defaultvalue=0): return (l + [defaultvalue] * n)[:n] 

Por supuesto, hacer eso con valores predeterminados mutables conduce a problemas, como ha encontrado.

La syntax:

 list=[any_object]*n 

es un atajo para hacer una lista de n mismos objetos. Como puede ver lo siguiente:

 >>> l=[foo()]*3 >>> l [<__main__.foo instance at 0x02D64D28>, <__main__.foo instance at 0x02D64D28>, <__main__.foo instance at 0x02D64D28>] >>> m=[ foo() for i in range(3)] >>> m [<__main__.foo instance at 0x02D64F08>, <__main__.foo instance at 0x02D613A0>, <__main__.foo instance at 0x02D611C0>] 

En el código anterior los tres objetos son iguales en el

lista l

como lo indiquen las ubicaciones de memoria allí o puede usar id (objeto) pero en la

lista m

ellos son diferentes. El punto es que cuando usas las comprensiones de listas, en realidad haces el proceso de crear tres objetos en una línea en lugar de usar un bucle para crear una instancia de los objetos y ponerlos en una lista, pero la list=[any_object]*n anterior list=[any_object]*n es solo un list=[any_object]*n de añadir el mismo objeto en una lista deseada número de veces.