python - El corte de tuplas no devuelve un nuevo objeto en comparación con el corte de listas
tuples slice (4)
En Python (2 y 3). Cada vez que usamos el corte de lista, devuelve un nuevo objeto, por ejemplo:
l1 = [1,2,3,4]
print(id(l1))
l2 = l1[:]
print(id(l2))
Salida
>>> 140344378384464
>>> 140344378387272
Si se repite lo mismo con tupla, se devuelve el mismo objeto, por ejemplo:
t1 = (1,2,3,4)
t2 = t1[:]
print(id(t1))
print(id(t2))
Salida
>>> 140344379214896
>>> 140344379214896
Sería genial si alguien puede arrojar algo de luz sobre por qué sucede esto, a lo largo de mi experiencia en Python, tuve la impresión de que un segmento vacío devuelve un nuevo objeto.
Tengo entendido que está devolviendo el mismo objeto ya que las tuplas son inmutables y no tiene sentido crear una nueva copia. Pero, de nuevo, no se menciona en los documentos en ninguna parte.
En Python 3. *
my_list[:]
es azúcar sintáctico para el
type(my_list).__getitem__(mylist, slice_object)
donde:
slice_object
es un objeto de
my_list
creado a partir de los atributos (longitud) de
my_list
y la expresión
[:]
.
Los objetos que se comportan de esta manera se denominan subscriptables en el modelo de datos de Python, ver
here
.
Para listas y tuplas,
__getitem__
es un método incorporado.
En CPython, y para listas y tuplas,
__getitem__
es interpretado por la operación de
BINARY_SUBSCR
que se implementa para las tuplas
aquí
y para las listas
aquí
.
En el caso de las tuplas, al recorrer el código, verá que en
este bloque de código
,
static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item)
devolverá una referencia al mismo
PyTupleObject
que obtuvo como argumento de entrada, si el ítem es de escriba
PySlice
y el corte se evalúa como la tupla completa.
static PyObject*
tuplesubscript(PyTupleObject* self, PyObject* item)
{
/* checks if item is an index */
if (PyIndex_Check(item)) {
...
}
/* else it is a slice */
else if (PySlice_Check(item)) {
...
/* unpacks the slice into start, stop and step */
if (PySlice_Unpack(item, &start, &stop, &step) < 0) {
return NULL;
}
...
}
/* if we start at 0, step by 1 and end by the end of the tuple then !! look down */
else if (start == 0 && step == 1 &&
slicelength == PyTuple_GET_SIZE(self) &&
PyTuple_CheckExact(self)) {
Py_INCREF(self); /* increase the reference count for the tuple */
return (PyObject *)self; /* and return a reference to the same tuple. */
...
}
Ahora examina el código para
static PyObject * list_subscript(PyListObject* self, PyObject* item)
y comprueba por ti mismo que sea cual sea el segmento, siempre se devuelve un nuevo objeto de lista.
Es un detalle de implementación.
Debido a que las listas son mutables,
l1[:]
debe
crear una copia, porque no esperaría que los cambios en
l2
afecten a
l1
.
Sin embargo, dado que una tupla es
inmutable
, no hay nada que pueda hacer a
t2
que afecte a
t1
de manera visible, por lo que el compilador es libre (pero no
obligatorio
) de usar el mismo objeto para
t1
y
t1[:]
.
Las implementaciones son libres de devolver instancias idénticas para tipos inmutables (en CPython, a veces puede ver optimizaciones similares para cadenas y enteros). Dado que el objeto no se puede cambiar, no hay nada en el código de usuario que deba preocuparse si contiene una instancia única o simplemente otra referencia a una instancia existente.
Puede encontrar el cortocircuito en el código C here .
static PyObject*
tuplesubscript(PyTupleObject* self, PyObject* item)
{
... /* note: irrelevant parts snipped out */
if (start == 0 && step == 1 &&
slicelength == PyTuple_GET_SIZE(self) &&
PyTuple_CheckExact(self)) {
Py_INCREF(self); /* <--- increase reference count */
return (PyObject *)self; /* <--- return another pointer to same */
}
...
Este es un detalle de implementación, tenga en cuenta que pypy no hace lo mismo.
No estoy seguro de esto, pero parece que Python le proporciona un nuevo puntero al mismo objeto para evitar la copia, ya que las tuplas son idénticas (y dado que el objeto es una tupla, es inmutable).