vacia una tuplas por metodos llenar listas lista elementos crear comprensión arreglos anidadas agregar python python-3.x list-comprehension python-internals generator-expression

una - ¿Las listas de comprensión son sintácticas de azúcar para `list(expresión del generador)` en Python 3?



metodos de listas en python (4)

Ambas formas crean y llaman a una función anónima. Sin embargo, el formulario de list(...) crea una función de generador y pasa el generador-iterador devuelto a la list , mientras que con el formulario, la [...] función anónima construye la lista directamente con los LIST_APPEND .

El siguiente código obtiene la salida de descompilación de las funciones anónimas para una comprensión de ejemplo y su correspondiente genexp-pass-to- list :

import dis def f(): [x for x in []] def g(): list(x for x in []) dis.dis(f.__code__.co_consts[1]) dis.dis(g.__code__.co_consts[1])

La salida para la comprensión es

4 0 BUILD_LIST 0 3 LOAD_FAST 0 (.0) >> 6 FOR_ITER 12 (to 21) 9 STORE_FAST 1 (x) 12 LOAD_FAST 1 (x) 15 LIST_APPEND 2 18 JUMP_ABSOLUTE 6 >> 21 RETURN_VALUE

La salida para el genexp es

7 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 11 (to 17) 6 STORE_FAST 1 (x) 9 LOAD_FAST 1 (x) 12 YIELD_VALUE 13 POP_TOP 14 JUMP_ABSOLUTE 3 >> 17 LOAD_CONST 0 (None) 20 RETURN_VALUE

En Python 3, ¿es una comprensión de lista simplemente azúcar sintáctica para una expresión generadora incorporada a la función de list ?

Por ejemplo, es el siguiente código:

squares = [x**2 for x in range(1000)]

en realidad convertido en el fondo en el siguiente?

squares = list(x**2 for x in range(1000))

Sé que la salida es idéntica, y Python 3 corrige los sorprendentes efectos secundarios de los espacios de nombres circundantes que las listas de comprensión tenían, pero en términos de lo que el intérprete de CPython hace bajo el capó, es el primero convertido al último, o hay alguna diferencia ¿En cómo se ejecuta el código?

Fondo

Encontré esta reclamación de equivalencia en la sección de comentarios de esta pregunta , y una búsqueda rápida en Google mostró la misma reclamación que se hizo here .

También se mencionó esto en la documentación de Novedades en Python 3.0 , pero la redacción es algo vaga:

También tenga en cuenta que las comprensiones de lista tienen diferentes semánticas: están más cerca del azúcar sintáctica para una expresión generadora dentro de un constructor de lista (), y en particular las variables de control de bucle ya no se filtran en el ámbito circundante.


Ambos funcionan de manera diferente, la versión de comprensión de lista toma la ventaja del LIST_APPEND byte especial LIST_APPEND que llama a PyList_Append directamente por nosotros. Por lo tanto, evita la búsqueda de atributos para list.append y función call a nivel de Python.

>>> def func_lc(): [x**2 for x in y] ... >>> dis.dis(func_lc) 2 0 LOAD_CONST 1 (<code object <listcomp> at 0x10d3c6780, file "<ipython-input-42-ead395105775>", line 2>) 3 LOAD_CONST 2 (''func_lc.<locals>.<listcomp>'') 6 MAKE_FUNCTION 0 9 LOAD_GLOBAL 0 (y) 12 GET_ITER 13 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 16 POP_TOP 17 LOAD_CONST 0 (None) 20 RETURN_VALUE >>> lc_object = list(dis.get_instructions(func_lc))[0].argval >>> lc_object <code object <listcomp> at 0x10d3c6780, file "<ipython-input-42-ead395105775>", line 2> >>> dis.dis(lc_object) 2 0 BUILD_LIST 0 3 LOAD_FAST 0 (.0) >> 6 FOR_ITER 16 (to 25) 9 STORE_FAST 1 (x) 12 LOAD_FAST 1 (x) 15 LOAD_CONST 0 (2) 18 BINARY_POWER 19 LIST_APPEND 2 22 JUMP_ABSOLUTE 6 >> 25 RETURN_VALUE

Por otro lado, la versión list() simplemente pasa el objeto generador al método __init__ la lista, que luego llama internamente a su método extend . Como el objeto no es una lista o tupla, CPython obtiene primero su iterador y luego simplemente agrega los elementos a la lista hasta que se agote el iterador :

>>> def func_ge(): list(x**2 for x in y) ... >>> dis.dis(func_ge) 2 0 LOAD_GLOBAL 0 (list) 3 LOAD_CONST 1 (<code object <genexpr> at 0x10cde6ae0, file "<ipython-input-41-f9a53483f10a>", line 2>) 6 LOAD_CONST 2 (''func_ge.<locals>.<genexpr>'') 9 MAKE_FUNCTION 0 12 LOAD_GLOBAL 1 (y) 15 GET_ITER 16 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 POP_TOP 23 LOAD_CONST 0 (None) 26 RETURN_VALUE >>> ge_object = list(dis.get_instructions(func_ge))[1].argval >>> ge_object <code object <genexpr> at 0x10cde6ae0, file "<ipython-input-41-f9a53483f10a>", line 2> >>> dis.dis(ge_object) 2 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 15 (to 21) 6 STORE_FAST 1 (x) 9 LOAD_FAST 1 (x) 12 LOAD_CONST 0 (2) 15 BINARY_POWER 16 YIELD_VALUE 17 POP_TOP 18 JUMP_ABSOLUTE 3 >> 21 LOAD_CONST 1 (None) 24 RETURN_VALUE >>>

Comparaciones de tiempo:

>>> %timeit [x**2 for x in range(10**6)] 1 loops, best of 3: 453 ms per loop >>> %timeit list(x**2 for x in range(10**6)) 1 loops, best of 3: 478 ms per loop >>> %%timeit out = [] for x in range(10**6): out.append(x**2) ... 1 loops, best of 3: 510 ms per loop

Los bucles normales son ligeramente lentos debido a la lenta búsqueda de atributos. Lo caché y otra vez.

>>> %%timeit out = [];append=out.append for x in range(10**6): append(x**2) ... 1 loops, best of 3: 467 ms per loop

Aparte del hecho de que la comprensión de la lista ya no filtra las variables, una diferencia más es que algo como esto ya no es válido:

>>> [x**2 for x in 1, 2, 3] # Python 2 [1, 4, 9] >>> [x**2 for x in 1, 2, 3] # Python 3 File "<ipython-input-69-bea9540dd1d6>", line 1 [x**2 for x in 1, 2, 3] ^ SyntaxError: invalid syntax >>> [x**2 for x in (1, 2, 3)] # Add parenthesis [1, 4, 9] >>> for x in 1, 2, 3: # Python 3: For normal loops it still works print(x**2) ... 1 4 9


En realidad, puede demostrar que los dos pueden tener diferentes resultados para demostrar que son inherentemente diferentes:

>>> list(next(iter([])) if x > 3 else x for x in range(10)) [0, 1, 2, 3] >>> [next(iter([])) if x > 3 else x for x in range(10)] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <listcomp> StopIteration

La expresión dentro de la comprensión no se trata como un generador, ya que la comprensión no maneja la StopIteration , mientras que el constructor de list sí lo hace.


No son lo mismo, list() evaluará lo que se le haya dado una vez que lo que está entre paréntesis haya terminado de ejecutarse, no antes.

El [] en python es un poco mágico, le dice a python que incluya todo lo que hay dentro de él como una lista, más como una sugerencia tipográfica para el idioma.