Comprendere la notazione delle fette

Ho bisogno di una buona spiegazione (i riferimenti sono un plus) sulla notazione slice di Python.

Per me, questa notazione ha bisogno di un po' di ripasso.

Sembra estremamente potente, ma non ho ancora capito bene come funziona.

Soluzione

È piuttosto semplice:

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

C'è anche il valore step, che può essere usato con qualsiasi dei precedenti:

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

Il punto chiave da ricordare è che il valore :stop rappresenta il primo valore che non è nella fetta selezionata. Quindi, la differenza tra stop e start è il numero di elementi selezionati (se step è 1, il default).

L'altra caratteristica è che start o stop può essere un numero negativo, il che significa che conta dalla fine dell'array invece che dall'inizio. Quindi:

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

Allo stesso modo, step può essere un numero 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 è gentile con il programmatore se ci sono meno elementi di quelli richiesti. Per esempio, se chiedete a[:-2] e a contiene solo un elemento, otterrete una lista vuota invece di un errore. A volte preferiresti l'errore, quindi devi essere consapevole che questo può accadere.

Relazione con l'oggetto slice()

L'operatore di affettamento [] è in realtà usato nel codice precedente con un oggetto slice() usando la notazione : (che è valida solo all'interno di []), cioè:

a[start:stop:step]

è equivalente a:

a[slice(start, stop, step)]

Gli oggetti Slice si comportano anche in modo leggermente diverso a seconda del numero di argomenti, in modo simile a range(), cioè sono supportati sia slice(stop) che slice(start, stop[, step]). Per evitare di specificare un dato argomento, si potrebbe usare None, in modo che, ad esempio, a[start:] sia equivalente a a[slice(start, None)] o a[::-1] sia equivalente a a[slice(None, None, -1)].

Mentre la notazione basata su : è molto utile per il semplice slicing, l'uso esplicito degli oggetti slice() semplifica la generazione programmatica dello slicing.

Commentari (7)

Il tutorial Python ne parla (scorrete un po' più in basso fino ad arrivare alla parte sullo slicing).

Anche il diagramma ASCII art è utile per ricordare come funzionano le fette:

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

Un modo per ricordare come funzionano gli slices è pensare agli indici come se puntassero tra i caratteri, con il bordo sinistro del primo carattere numerato 0. Quindi il bordo destro dell'ultimo carattere di una stringa di n caratteri ha indice n.

Commentari (2)

Enumerare le possibilità permesse dalla grammatica:

>>> 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]]

Naturalmente, se (high-low)%stride != 0, allora il punto finale sarà un po' più basso di high-1.

Se stride è negativo, l'ordinamento è cambiato un po', dato che stiamo contando alla rovescia:

>>> 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]]

Gli affettamenti estesi (con virgole ed ellissi) sono per lo più utilizzati solo da strutture dati speciali (come NumPy); le sequenze di base non li supportano.

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