Traversing Iterables
00:00
So far in this course, you’ve gained a detailed understanding of how the zip()
function works in both Python 2 and Python 3, but you haven’t really seen it in practical use yet. In this lesson, I’ll show you the most common use case for zip()
, which is traversing iterables in parallel.
00:16
I wrote lists and dictionaries here, but of course, it can be any kind of iterables. These are just the examples that I’ll use. Here are a couple of basic patterns for iterating through multiple objects with zip()
., And really, these two are the same pattern—it’s just that in the second case, you have to do a little bit more work to deal with the added complexity of dictionaries. So, the idea is just that when you use zip()
, the output is an iterator of tuples, right?
00:45
Python has the convenient facility to be able to unpack tuples even in the middle of for
loops, and so you can actually unpack all of those corresponding items from each iterable into their own named objects that you can assign names to.
01:03
And so if you assign those meaningful names, it becomes really convenient because you don’t have to mess with actually indexing into the tuple as you would if you were working with a less flexible data structure. With dictionaries, the idea is the same thing, except a way to make it a little bit easier is to use the .items()
feature of dictionaries—the .items()
method of the dictionary object—which allows you to get an actual iterable of tuples containing the key and the value for each key in the dictionary.
01:33 Then, you can iterate through parallel dictionaries and have all of those keys and values available to you as you move through it. So, let’s take a trip over into the REPL, and I think this will become even more clear with a couple of examples.
01:48 I have a few lists here that’s meant to mimic a small hardware store, or something like that. I have a list of tools and their corresponding prices, and then the corresponding volume of sales—so, the number of these that I’ve sold in the last however long, right?
02:03
If I want to iterate through a couple of these lists, the most obvious way from the previous slide is for tool, price
—and I’ll just use these two for the moment—for tool, price in zip(tools, prices):
and then maybe you could do some kind of catalog thing here and you could say print
, f"the tool {tool} is available for"
—this many dollars, so—f"${price}"
, and that works perfectly well.
02:33 It’s a really nice way to do this, and it gives you convenient access to the corresponding items without any trouble, and they have nice, unique, meaningful names corresponding to each of these tuple items, which otherwise would just be kind of bare tuple indices.
02:46 So this is super convenient and easy.
02:50
Now you can also do some calculations on this, so you could say something like for tool, price, sales in zip(tools, prices, volume)
. I probably should’ve named volume
differently, but that’s okay, because I can essentially reassign this name for each individual element as I wish in this for
loops, so that’s really easy.
03:13
Now, I could say something here, like f"The total sales of tool {tool} for the last month is"
—and then I can insert some Python code here and just say f"{sales * price}"
.
03:31
And so this is a really great way to do some calculations here. So, the total sales of tool Hammer
is $189.89, and $149.50 of Nails
, and so on.
03:41
This is a really easy way to get access to this data without having to do any indexing logic, or anything like that. So, that’s the real power of the zip()
function in this case, is that you just get super easy access to everything you need when you have this structure of parallel iterables. So that’s really convenient. Next, let’s take a look at an example with dictionaries.
04:03 I’ve made a couple of example dictionaries that are meant to represent kind of a basic user structure that you might have for some kind of application, right?
04:13
So, I have a users
dictionary that maps usernames to user IDs, and then I have a utypes
(user types) dictionary that maps those user IDs to the type of user that they are—whether they’re an admin, whether they’re a normal user, and so on.
04:28
So, how can you use zip()
to deal with this? Well, I think it’s best to think of this in terms of what problem does zip()
solve for you here? So, the problem when you have data like this is that if you want to get the type of a user, when you have a username, what you need to say is something like utypes
at users
at username—oh, not "Hames"
but "James"
.
04:52 This is workable, but it’s a little bit clunky. You’re going to have to always remember, you know, which of these to put inside the other in terms of brackets and so on.
05:02
It’s a little bit clunky. So, one way to solve this problem—and there are many—is to say for (username, uid)
and then another tuple here, (uid2, utype)
in zip
, users.items()
—and this is a pretty long statement—utypes.items()
.
05:25
What you can do is you can say something like this: print("User with name", username, "and type", utype)
.
05:35
And so using this zip()
paradigm, you can actually unpack these parallel dictionary structures and get access to all of the information contained in them all at once without having to do any of this kind of clumsy logic.
05:51 Actually, then, what’s really even nicer about it is that you can unpack all of this into a nice structure where you don’t even have to worry about plugging in the individual name anymore.
06:02
That’s, I think, the thing that’s most useful is that you have access to this data, and not only do you have access to it but you have it with coherent names rather than utypes[users["James1"]]
, right? Where instead, you just have the utype
value, which is super obviously the 'Admin'
. So, that’s a really useful thing.
06:20
And why does it work, exactly? Well, if you take a look at users.items()
,
06:25
you can see that it’s a dict_items
object with an internal list with two tuples, which are the key and the value for each key in your dictionary, right?
06:35
And so if you take a look at utypes.items()
,
06:39 you can see it’s the same thing, and so when you zip it together, both of the items at the zeroth key index are going to be together. And luckily, in Python versions later than 3.5 dictionaries have an ordering that stays the same throughout whenever you call items or something like this, so you know that the order that you put them in is in fact the same order that they’ll stay in.
07:04
This isn’t guaranteed in Python versions earlier than 3.5, so you should be careful with that as you work through this. But this is how you interact with dictionaries using the zip()
pattern.
07:16 I have one more thing that I want to show you, which is how to unzip after you’ve zipped something.
07:23
When I was getting started with Python, I was always confused why there wasn’t an unzip function that was the reverse if zip()
. But the cool thing about zip()
is that it’s actually its own opposite.
07:35
You just have to combine it with the unpacking operator, the asterisk operator (*
). And what that does is when you call zip()
on *pairs
, what you actually get is all of the items in pairs
—so, all of these tuples.
07:50
It’s like they’re being passed into zip()
as individual arguments, so it’s as if you just said zip()
this tuple, this tuple—all the way down to the end, right? And so with that in mind, what do you think the code above should do?
08:04
What should actually be the result for numbers
and letters
?
08:09
Well, if you said that numbers
should be all the numbers in one tuple, and letters
should be all the letters in another, then you’re totally right. Though, to be fair, the numbers
and letters
names were kind of hints that way, ha. But well done.
08:23
And so the reason that this works is because if you call zip()
on four tuples, then of course, via the behavior of zip()
, it takes all of the elements at the zeroth index—so, the element at the zeroth index for each tuple—and packages them into one tuple, so that’s numbers
. And then takes all the elements at the first index and passes those and creates a tuple out of those, and that becomes letters
.
08:49
And of course, when you assign numbers
and letters
to the output of zip()
, they’re naturally—through the unpacking process, those get assigned to numbers
and letters
.
08:58
So, it’s pretty amazing how that process works. It can definitely be a little bit mind-bending—that was something that I definitely felt when I was starting out with Python—how zip()
can be its own opposite. But keep this process in mind, the zip()
and the unpacking operator, and that that is really how you do this unzipping process. In the next lesson, I’m going to take you through some even more, kind of, cool and idiosyncratic and advanced ways to use zip()
to get some cool behavior in your Python code.
Liam Pulsifer RP Team on Aug. 2, 2020
Right @tobenary, this example is definitely a little contrived and shouldn’t be treated as the end-all, be-all of user role storage systems :p Luckily, I think your comment should serve as a great example of the limitations here for future viewers!
gmodelgado on Sept. 5, 2020
You can’t fix it with zip_longest
neither.
Liam Pulsifer RP Team on Sept. 8, 2020
Right @gmodelgado – as I mentioned earlier, it’s best to treat this “user management system” as a toy example for the purpose of showing the features of zip. When you start adding mappings from many users to a smaller set of user roles, zip becomes the wrong tool for the job and it’s better to stick with simpler dictionary logic.
Become a Member to join the conversation.
tobenary on July 17, 2020
Section 4 - “Traversing Iterables” > 07:11 minute: You gave an example of two dictionaries, while it was best to show it as 3 like you did on the earlier section.
So, I tried to do it and see what is the output:
While Jonny was left out. But, this is what we will have eventually on feild, many names, but 2 or 3 roles.
I think that for newbies, you should also include an additional comment for it.