Entender la notación de las rodajas

Necesito una buena explicación (las referencias son un plus) sobre la notación slice de Python's.

Para mí, esta notación necesita un poco de recoger.

Parece extremadamente potente, pero no he conseguido entenderla.

Solución

En realidad es muy sencillo:

a[start:stop]  # items start through stop-1
a[start:]      # items start through the rest of the array
a[:stop]       # items from the beginning through stop-1
a[:]           # a copy of the whole array

También existe el valor step, que se puede utilizar con cualquiera de los anteriores:

a[start:stop:step] # start through not past stop, by step

El punto clave que hay que recordar es que el valor :stop representa el primer valor que no está en la porción seleccionada. Así, la diferencia entre stop y start es el número de elementos seleccionados (si step es 1, el valor por defecto).

La otra característica es que start o stop puede ser un número negativo, lo que significa que cuenta desde el final del array en lugar del principio. Así:

a[-1]    # last item in the array
a[-2:]   # last two items in the array
a[:-2]   # everything except the last two items

Del mismo modo, step puede ser un número negativo:

a[::-1]    # all items in the array, reversed
a[1::-1]   # the first two items, reversed
a[:-3:-1]  # the last two items, reversed
a[-3::-1]  # everything except the last two items, reversed

Python es amable con el programador si hay menos elementos de los que pide. Por ejemplo, si pides a[:-2] y a sólo contiene un elemento, obtienes una lista vacía en lugar de un error. A veces se prefiere el error, así que hay que tener en cuenta que esto puede ocurrir.

Relación con el objeto slice()

El operador de corte [] está siendo utilizado en el código anterior con un objeto slice() utilizando la notación : (que sólo es válida dentro de []), es decir:

a[start:stop:step]

equivale a:

a[slice(start, stop, step)]

Los objetos Slice también se comportan de forma ligeramente diferente dependiendo del número de argumentos, de forma similar a range(), es decir, se admiten tanto slice(stop) como slice(start, stop[, step]). Para omitir la especificación de un argumento determinado, se puede utilizar None, de modo que, por ejemplo, a[inicio:] es equivalente a a[slice(inicio, None)] o a[::-1] es equivalente a a[slice(None, None, -1)].

Aunque la notación basada en : es muy útil para el corte simple, el uso explícito de objetos slice() simplifica la generación programática del corte.

Comentarios (7)

El tutorial de Python habla de ello (desplázate un poco hacia abajo hasta que llegues a la parte del corte).

El diagrama de arte ASCII también es útil para recordar cómo funcionan las rebanadas:

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1

Una forma de recordar cómo funcionan las rodajas es pensar en los índices como si apuntaran entre caracteres, con el borde izquierdo del primer carácter numerado como 0. Entonces el borde derecho del último carácter de una cadena de n caracteres tiene el índice n.

Comentarios (2)

Enumerar las posibilidades que permite la gramática:

>>> seq[:]                # [seq[0],   seq[1],          ..., seq[-1]    ]
>>> seq[low:]             # [seq[low], seq[low+1],      ..., seq[-1]    ]
>>> seq[:high]            # [seq[0],   seq[1],          ..., seq[high-1]]
>>> seq[low:high]         # [seq[low], seq[low+1],      ..., seq[high-1]]
>>> seq[::stride]         # [seq[0],   seq[stride],     ..., seq[-1]    ]
>>> seq[low::stride]      # [seq[low], seq[low+stride], ..., seq[-1]    ]
>>> seq[:high:stride]     # [seq[0],   seq[stride],     ..., seq[high-1]]
>>> seq[low:high:stride]  # [seq[low], seq[low+stride], ..., seq[high-1]]

Por supuesto, si (high-low)%stride != 0, entonces el punto final será un poco más bajo que high-1.

Si stride es negativo, el ordenamiento se cambia un poco ya que estamos contando hacia abajo:

>>> seq[::-stride]        # [seq[-1],   seq[-1-stride],   ..., seq[0]    ]
>>> seq[high::-stride]    # [seq[high], seq[high-stride], ..., seq[0]    ]
>>> seq[:low:-stride]     # [seq[-1],   seq[-1-stride],   ..., seq[low+1]]
>>> seq[high:low:-stride] # [seq[high], seq[high-stride], ..., seq[low+1]]

Las secuencias extendidas (con comas y elipses) son usadas mayormente sólo por estructuras de datos especiales (como NumPy); las secuencias básicas no las soportan.

>>> class slicee:
...     def __getitem__(self, item):
...         return repr(item)
...
>>> slicee()[0, 1:2, ::5, ...]
'(0, slice(1, 2, None), slice(None, None, 5), Ellipsis)'
Comentarios (3)