python list tuples slice cpython

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).