¿Por qué es []
más rápido que list()
?
La razón más importante es que Python se trata list()
como una función definida por el usuario, lo que significa que puede interceptarla aliasando algo más list
y hacer algo diferente (como usar su propia lista subclasificada o tal vez una deque).
Inmediatamente crea una nueva instancia de una lista integrada con []
.
Mi explicación busca darte la intuición para esto.
Explicación
[]
se conoce comúnmente como sintaxis literal.
En la gramática, esto se conoce como "visualización de lista". De los documentos :
Una visualización de lista es una serie posiblemente vacía de expresiones encerradas entre corchetes:
list_display ::= "[" [starred_list | comprehension] "]"
Una visualización de lista produce un nuevo objeto de lista, el contenido se especifica mediante una lista de expresiones o una comprensión. Cuando se proporciona una lista de expresiones separadas por comas, sus elementos se evalúan de izquierda a derecha y se colocan en el objeto de la lista en ese orden. Cuando se suministra una comprensión, la lista se construye a partir de los elementos resultantes de la comprensión.
En resumen, esto significa que list
se crea un objeto de tipo incorporado .
No hay forma de eludir esto, lo que significa que Python puede hacerlo tan rápido como sea posible.
Por otro lado, list()
se puede interceptar creando un generador incorporado list
utilizando el constructor de la lista integrada.
Por ejemplo, supongamos que queremos que nuestras listas se creen ruidosamente:
class List(list):
def __init__(self, iterable=None):
if iterable is None:
super().__init__()
else:
super().__init__(iterable)
print('List initialized.')
Luego podríamos interceptar el nombre list
en el ámbito global del nivel del módulo, y luego cuando creamos un list
, en realidad creamos nuestra lista de subtipos:
>>> list = List
>>> a_list = list()
List initialized.
>>> type(a_list)
<class '__main__.List'>
Del mismo modo, podríamos eliminarlo del espacio de nombres global
del list
y ponerlo en el espacio de nombres incorporado:
import builtins
builtins.list = List
Y ahora:
>>> list_0 = list()
List initialized.
>>> type(list_0)
<class '__main__.List'>
Y tenga en cuenta que la visualización de la lista crea una lista incondicionalmente:
>>> list_1 = []
>>> type(list_1)
<class 'list'>
Probablemente solo hagamos esto temporalmente, así que deshazcamos nuestros cambios: primero elimine el nuevo List
objeto de los archivos incorporados:
>>> del builtins.list
>>> builtins.list
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'builtins' has no attribute 'list'
>>> list()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'list' is not defined
Oh, no, perdimos el rastro del original.
No se preocupe, todavía podemos obtenerlo list
: es el tipo de una lista literal:
>>> builtins.list = type([])
>>> list()
[]
Entonces...
¿Por qué es []
más rápido que list()
?
Como hemos visto, podemos sobrescribir list
, pero no podemos interceptar la creación del tipo literal. Cuando usamos list
tenemos que hacer las búsquedas para ver si hay algo allí.
Luego tenemos que llamar a cualquier llamada que hayamos buscado. De la gramática:
Una llamada llama a un objeto invocable (por ejemplo, una función) con una serie de argumentos posiblemente vacía:
call ::= primary "(" [argument_list [","] | comprehension] ")"
Podemos ver que hace lo mismo con cualquier nombre, no solo con la lista:
>>> import dis
>>> dis.dis('list()')
1 0 LOAD_NAME 0 (list)
2 CALL_FUNCTION 0
4 RETURN_VALUE
>>> dis.dis('doesnotexist()')
1 0 LOAD_NAME 0 (doesnotexist)
2 CALL_FUNCTION 0
4 RETURN_VALUE
Para []
no hay llamada de función en el nivel de código de bytes Python:
>>> dis.dis('[]')
1 0 BUILD_LIST 0
2 RETURN_VALUE
Simplemente va directamente a la construcción de la lista sin búsquedas ni llamadas a nivel de bytecode.
Conclusión
Hemos demostrado que list
puede ser interceptado con código de usuario utilizando las reglas de alcance, y que list()
busca un invocable y luego lo llama.
Mientras que []
es una visualización de lista, o un literal, y por lo tanto evita la búsqueda de nombre y la llamada a función.
()
y''
son especiales, ya que no solo están vacíos, son inmutables y, como tal, es una victoria fácil hacerlos solteros; ni siquiera construyen nuevos objetos, solo cargan el singleton para eltuple
/ vacíostr
. Técnicamente es un detalle de implementación, pero me cuesta imaginar por qué no almacenarían en caché el vacíotuple
/str
por razones de rendimiento. Por lo tanto, su intuición acerca de[]
y{}
devolver un literal de acciones estaba mal, pero se aplica a()
y''
.