python - data - ¿Por qué ''() es()'' devuelve True cuando ''[] es[]'' y ''{} es{}'' return False?
python data types list (1)
Por lo que he tenido conocimiento, usar [], {}
o ()
para crear instancias de objetos devuelve una nueva instancia de list, dict
o tuple
respectivamente; un nuevo objeto de instancia con una nueva identidad .
Esto fue bastante claro para mí hasta que realmente lo probé y noté que () is ()
realidad devuelve True
lugar del False
esperado:
>>> () is (), [] is [], {} is {}
(True, False, False)
como se esperaba, este comportamiento también se manifiesta cuando se crean objetos con list()
, dict()
y tuple()
respectivamente:
>>> tuple() is tuple(), list() is list(), dict() is dict()
(True, False, False)
La única información relevante que pude encontrar en los documentos para tuple()
dice:
[...] Por ejemplo, regresa
(1, 2, 3)
tuple(''abc'')
regresa(''a'', ''b'', ''c'')
ytuple([1, 2, 3])
(1, 2, 3)
. Si no se da ningún argumento, el constructor crea una nueva tupla vacía,()
.
Basta decir que esto no es suficiente para responder mi pregunta.
Entonces, ¿por qué las tuplas vacías tienen la misma identidad, mientras que otras, como listas o diccionarios, no?
En breve:
Python crea internamente una lista C
de objetos de tupla cuyo primer elemento contiene la tupla vacía. Cada vez que se usa tuple()
o ()
, Python devolverá el objeto existente contenido en la lista C
antes mencionada y no creará uno nuevo.
Tal mecanismo no existe para los objetos dict
o list
que, por el contrario, se vuelven a crear desde cero cada vez .
Esto probablemente esté relacionado con el hecho de que los objetos inmutables (como las tuplas) no se pueden alterar y, como tales, se garantiza que no cambien durante la ejecución. Esto se solidifica aún más cuando se considera que frozenset() is frozenset()
devuelve True
; like ()
un frozenset
vacío se considera un singleton en la implementación de CPython
. Con los objetos mutables, tales garantías no están vigentes y, como tal, no hay ningún incentivo para almacenar en caché sus instancias de elemento cero (es decir, su contenido podría cambiar si la identidad sigue siendo la misma).
Tome nota: esto no es algo de lo que uno debería depender, es decir, uno no debería considerar las tuplas vacías como singletons. No se hacen explícitas tales garantías en la documentación, por lo que se debe suponer que depende de la implementación.
Como esta hecho:
En el caso más común, la implementación de CPython
se compila con dos macros PyTuple_MAXFREELIST
y PyTuple_MAXSAVESIZE
establecidas en enteros positivos. El valor positivo para estas macros da como resultado la creación de una matriz de objetos de tuple
con el tamaño PyTuple_MAXSAVESIZE
.
Cuando se llama a PyTuple_New
con el size == 0
parámetro size == 0
se asegura de agregar una nueva tupla vacía a la lista si aún no existe:
if (size == 0) {
free_list[0] = op;
++numfree[0];
Py_INCREF(op); /* extra INCREF so that this is never freed */
}
Luego, si se solicita una nueva tupla vacía, la que se encuentra en la primera posición de esta lista se devolverá en lugar de una nueva instancia:
if (size == 0 && free_list[0]) {
op = free_list[0];
Py_INCREF(op);
/* rest snipped for brevity.. */
Una razón adicional que causa un incentivo para hacer esto es el hecho de que las llamadas a funciones construyen una tupla para contener los argumentos posicionales que se van a usar. Esto se puede ver en la función ceval.c
en ceval.c
:
static PyObject *
load_args(PyObject ***pp_stack, int na)
{
PyObject *args = PyTuple_New(na);
/* rest snipped for brevity.. */
que se llama a través de do_call
en el mismo archivo. Si el número de argumentos na
es cero, se devolverá una tupla vacía.
Básicamente, esta podría ser una operación que se realiza con frecuencia, por lo que tiene sentido no reconstruir una tupla vacía cada vez.
Otras lecturas:
Un par de respuestas más arrojan luz sobre el comportamiento de caché de CPython
con inmutables: