While “for” loops in Python don’t explicitly use numeric indexes, slices do. My students often ask me how, without indexes, they can iterate over only part of a string (or other sequence). The answer is to use a slice. For example, if I’m interested in iterating over letters 3-7 in my string “s” from above, I won’t (and can’t) use a C-like “for” loop, starting the loop at index 3 and ending at index 7. Rather, I need to run my “for” loop over a complete string… which a slice just happens to return to me:

for one_letter in s[3:7]: # s[3:7] returns a new string print(one_letter)

Remember that a slice from type X will always return a new object of type X. Thus, slicing a string returns a string, while slicing a list returns a list and slicing a tuple returns a tuple. For example:

Slice syntax is more flexible than this: You can leave off the starting index, ending index, or both to indicate that you want to go all the way to the extreme edge:

>>> s'abcdefghij'>>> s[:5] # from the start until (and not including) index 5'abcde'>>> s[5:] # from index 5 until (and through) the end'fghij'>>> s[5:-1] # not the same as s[5:] -- doesn't include the end'fghi'

Because slices create new objects, you can sometimes use them to avoid problems with mutable data:

Note that this isn’t a perfect solution; if you’re copying complex data structures, then you’ll probably want to look at the “copy” module, and explore its “copy” and “deepcopy” methods.

By default, a slice uses each element from the sequence on which it’s working. But what if we only want every other element? Then we can use the (optional) third part of slice syntax, known as either the “step size” or the “stride.” By default, the step size is 1. But by adding another number after a second colon, we can modify this, too:

It gets even better when you discover that the step size can be negative, which allows us to retrieve values in reverse order from the original data structure. Just remember, in such cases, that the start needs to be bigger than the end:

Notice how our “slice” object works just like the start:end:step syntax; if you want to indicate the edge (via nothing between the colons), then you can use “None”. Again, I’m not really sure why you would need a slice object, but it’s nice to know that everything in Python is indeed an object, and that the :: syntax is translated into a slice object in the end.

What if you want your own objects to be sliceable? Truth be told, there’s not much to do: The __getitem__ method is used for retrieving individual items as well as slices; while there used to be a __getslice__ method, nowadays you are expected to write __getitem__ such that it handles individual indexes and slices. In many cases, that’s trivially easy to do:

Of course, if you want to do something more sophisticated than returning one or many elements from your object, then you’ll have to work a bit harder. But with an if/else, you can make such decisions, and then return the appropriate data.

Slices are both common and convenient ways to extract portions of Python data structures — usually with builtin objects, but also on your own. Once you get used to slices, you’ll see lots of uses for them, and wonder how you got along without them.