### What is Slice Notation in Python?

Python Slicing is a flexible tool to build new lists out of an existing list. Python supports slice notation for any sequential data types like lists, tuples, strings, bytes, bytearrays, and ranges. Also, any new data structure can also be added to its support as well. This is greatly used in Pandas and NumPy libraries, which are so popular in Machine Learning and Data Science. This is a very good example of “learn once, use everywhere”.

### Python Slicing Notation Examples:

a[start:stop] # items from start index to stop-1 index
a[start:] # items from start index to the rest of the array
a[:stop] # items from the beginning to stop-1 index
a[:] # a copy of the whole array
There is also a step value, which can be used in any of the above notations:

a[start:stop:step] # start through not past stop, by step
The best way to remember this is that the :stop value represents the first value that is not in the selected slice. So, the difference between stop and start will be the number of elements selected (if step is 1, the default).

The ASCII art diagram is helpful too for remembering how slices work:

+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1
Another way to remember how slice notation works is to think of the indices as pointing between characters, with the left edge of the first character numbered as 0. Then the right edge of the last character of a string of n characters has index n.

Let's take an example to understand slice assignment:

+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
Slice position: 0 1 2 3 4 5 6
Index position: 0 1 2 3 4 5
>>> p = ['P','y','t','h','o','n']
# Why the two sets of numbers:
# indexing gives items, not lists
>>> p[0]
'P'
>>> p[5]
'n'
# Slicing gives lists
>>> p[0:1]
['P']
>>> p[0:2]
['P','y']
>>> p[5] # the last index of six items, indexed from zero
'n'
>>> p[0:5] # this does NOT include the last item
['P','y','t','h','o']
>>> p[0:6] # not p[0:5]!!!
['P','y','t','h','o','n']
>>> p[0:4] # Start at the beginning and count only 4 items (index starts from 0)
['P','y','t','h']
>>> p[1:4] # Take out one item off the front and upto (4-1) index
['y','t','h']
>>> p[2:4] # Take two items off the front and upto (4-1) index
['t','h']
The other feature in slice notation is that start or stop may be a negative number too, which means it counts from the end of the array instead of the beginning. So:

a[-1] # respresents last item in the array
a[-2:] # represents last two items in the array
a[:-2] # everything from start except the last two items
Similarly, step may be a negative number too:

a[::-1] # all items in the array, reversed order
a[1::-1] # the first two items, reversed order
a[:-3:-1] # the last two items, reversed order
a[-3::-1] # everything except the last two items, reversed order
Python is also very kind to the programmer if there are fewer items than you ask for. For example, if you ask for a[:-2] from list a and a only contains one element, you get an empty list instead of an error. But Sometimes you would prefer the error, so you have to be aware that this may happen too.

### Relation to slice() object

The slicing operator [] is really being used in the above code with a slice() object using the : notation (which is only valid within []), i.e.:

a[start:stop:step]
is equivalent to:

a[slice(start, stop, step)]
Slice objects also behave slightly different depending on the number of arguments it has, similarly to range(), i.e. both slice(stop) and slice(start, stop[, step]) are supported. To skip specifying a given argument, a programmer might use None, so that e.g. a[start:] is equivalent to a[slice(start, None)] or a[::-1] is equivalent to a[slice(None, None, -1)].

While the :-based notation is very helpful for simple slicing operations, the explicit use of slice() objects simplifies the programmatic generation of slicing.