Locked learning resources

You must own this product to watch this lesson.

Locked learning resources

You must own this product to watch this lesson.

Writing Pythonic Loops

00:00 Hey there, this is Dan Bader. And today I want to talk about how you can make your loops more Pythonic. I know a lot of people are migrating to Python from different language backgrounds, and one of the things that makes it very easy to spot someone with a background in, let’s say, a C-style language—like C, C++, Java, C#—what makes them very easy to spot is how they write their loops.

00:27 In this case here, I’ve got this list with three items, and I want to iterate over that so I can print out each item. This is the end result we want to get, here.

00:37 And if you see someone writing a loop like this in Python—let’s say, during an interview situation, or when you’re reviewing someone’s code—it becomes very clear that this person is not super familiar with how a loop like that would be written in Python.

00:53 It looks like a C-style, Java-style loop, right? We’re keeping track of this index variable here, and we’re taking the length of that list to iterate over it, and then we’re also accessing each element based on the index, and of course, we need to keep track of our loop variable here—so, not very Pythonic.

01:15 So what I’m going to do in this video is I’m going to go over this loop and I’m going to refactor it. I’m going to go over all of these items that I just called out and I’m going to make sure we’re going to make this loop as Pythonic as possible, and really make this seem like a loop that a native Python developer would have written.

01:35 The first thing I’m going to change here is how this loop was keeping track of the loop index manually—so we’re incrementing this little counter variable called i, and that’s not very Pythonic.

01:47 Thankfully, there’s a way in Python to do that automatically using the range() builtin. So in this case, I’m going to call the range() function with the length of my container—which is not ideal, just kind of foreshadowing some things, but we’re going to change that right after. So, if I call this, what I get back is a range object. Now, a range object is a generator in Python, so it’s going to be generating these individual indexes from 0 all the way to 2—because it’s not going to include the right-hand-side element.

02:19 And actually, if you want to see what this looks like, I can call list() on this range object, and that’s going to kind of go through the whole generator and it’s going to give me a list with the elements generated by the generator.

02:32 So as you can see here, this range object would go from 0 all the way to 3 - 1, which is 2. And what I can do now is I can refactor that loop using the built-in range() function, and that allows me to get rid of the index tracking.

02:48 Again, this is going to work exactly the same—what you can see here now, we’re using this range object to generate these loop indexes one by one.

02:57 Now by the way, if you’re on Python 2, you’ll want to use the xrange() builtin to actually get a generator because if you’re using range() in Python 2, it will pre-create the whole range as an actual list, which is going to be bad for memory usage. So, if you’re going to create a huge range in Python 2, this would actually pre-create all of those elements, which is probably not what you want. So, on Python 2 you’ll want to use the xrange() builtin. It does exactly the same as range(), but the key difference is that it actually returns a generator versus returning the actual list. Now, if you took this refactored loop and you showed it to a Python programmer, they would probably cringe a little bit because this is really not ideal yet.

03:37 This could be simplified further. I mean, we’re a little bit better because we got rid of this manual index tracking, but there’s more we can do. So actually, a key thing to realize is that for loops in Python are really more like for-each loops in other languages.

03:54 You can use them to iterate over items from a container or sequence directly, and you don’t have to look up each individual item by index. So, I can take this loop and simplify it even further by taking advantage of that. This is what it would look like: I would just go for item in my_items, which is nice because now we’re actually giving a real name to this variable here, calling it item. We completely got rid of the i and the indexing here, and this is going to work just the same.

04:25 So, this implementation is much more Pythonic. It got rid of the need to actually use the range() builtin. We’re not having to call len() to get the length of that list, but instead, we’re just going to iterate over that container and the container itself is going to take care of handing out these elements so we can process them. So, if the container is ordered, like in a list, the resulting elements that we’re iterating over are also going to be ordered.

04:49 And if the container is not ordered, then it will return the elements kind of in an arbitrary order. So, this is going to give us all the elements, but there’s no guarantees how we’re iterating over them, depending on the container that you use. But these two examples here, because we’re dealing with a list, they are exactly the same.

05:07 Like, we’re getting exactly the same resulting printout here. Now, maybe you’re wondering, “What should I do if I need the item index?” Right? You might not always be able to rewrite your loops that way, with a for-each loop, if you actually need the item index.

05:23 And thankfully, there’s a Pythonic way to keep that running index that allows us to avoid this kind of odd-looking range of length of something construct.

05:36 And for that, we have the built-in enumerate() function. So as you can see here, I’ve changed this for loop a little bit. So now, I’m calling enumerate() on this list here, and enumerate() is another generator.

05:48 What it’s going to do is it’s going to return a running index and then also the actual element. And you can see that here. So, it’s returning the index first, and then the actual item in that container.

06:00 So it’s, well, enumerating the container. And I can take advantage of that. If I ever need the actual index, I can use enumerate() and that would be the Pythonic way to deal with this situation, versus keeping track of that index manually.

06:15 Let’s take a look at what this does when we actually run it. All right, so this iterated over the whole container and it was keeping track of a running index, and it was also printing each item individually. So again, enumerate() is very handy for those kinds of situations.

06:30 So, I really want to make everyone happy with this video about writing more Pythonic loops, and I know that a common question is, “Hey, so what if I absolutely have to write a C-style loop? Like, what if I have this and I need to convert that to Python in some way, maybe because I’m using a different step size here, how would I do this? Just assuming I would have to do this, how can I do this?” And for that, we’re going to come back to the range() function.

07:02 So what you would do here is you would use the range() function in a way that actually maps pretty closely to the C-style loop here. range() can take several more parameters, so you can pass the initial value to it, which I called a here, and you can pass it the number—like, the right-hand-side value in that range, or sort of the number of elements that you’re iterating over.

07:27 I called it n here, and you can see how that maps. Then, you can also pass a third parameter that is going to be the step size. So, with that s parameter, you can influence how big of a step the range() is going to take between elements.

07:42 So if you want to iterate over every 10th element in the list, you would just use a step size of 10, and then instead of iterating over each element individually, you would jump ahead 10 elements at a time.

07:54 And that would allow you to pretty much adapt any kind of C-style for loop that is using this indexing stuff, and you would be able to take that and turn it into a sort of Pythonic loop.

08:06 Now to wrap this up, I just want to reiterate some of the key points in this video. So first of all, writing C-style loops in Python is considered un-Pythonic.

08:15 It’s not a very natural way to do that in Python, and so if possible, you should try and avoid managing loop indexes and these stop conditions manually. The other thing is that Python’s for loops are really for-each loops, so they can iterate over items from a container or a sequence directly, and just kind of consume items from that container and then hand them to you.

08:41 And you should really take advantage of that if you’re writing Python code because then you don’t need to manage all of these indexes, index lookups, and keeping track of that manually—you don’t have to do that.

08:54 Python will do it for you, and that way you can reduce the amount of code you need to write, the amount of code you need to manage. That’s a great way for you to simplify your programs. And using these Python-style loops also makes it harder to introduce bugs, right?

09:08 You can’t really be off by one, or you can’t be running past the end of a container if you’re using a for-each style loop. So I would really encourage you to use them. Cool!

09:19 Well, best of luck and happy Pythoning!

Avatar image for csabinho

csabinho on March 25, 2022

My only, really minor, remark would be that I would call the end-variable b instead of n, as n is usually, in maths etc., the number of items and you called your start variable a, so it would match your naming pattern.

You must own this product to join the conversation.