Zip creates an iterator, how can we turn that into a dictionary?

why the dictionary is empty when i execute this lines of code?

drinks = ["espresso", "chai", "chai", "drip"]
caffeine = [64, 40, 0, 120]
zipped_drinks = zip(drinks, caffeine)
print("Zipped drinks ",list(zipped_drinks))
drinks_to_caffeine = {key: value for key, value in zipped_drinks}
print("Drinks to caffeine dictionary ",drinks_to_caffeine)

output:

Zipped drinks [(‘espresso’, 64), (‘chai’, 40), (‘chai’, 0), (‘drip’, 120)]
Drinks to caffeine dictionary {}

1 Like

The object zipped_drinks is an iterator. It is characteristic of an iterator, that you have one go at it: after you have iterated through it one time, it cannot be re-used.

So, when you call print("Zipped drinks ",list(zipped_drinks)), the iterator zipped_drinks is exhausted by providing the printable list to the print function.

Solution: Either comment out the first print() function, or assign the list to a variable and call print() and the dictionary comprehension on the variable.

drinks = ["espresso", "chai", "chai", "drip"]
caffeine = [64, 40, 0, 120]
zipped_drinks = zip(drinks, caffeine)
drink_list = list(zipped_drinks)
print("Zipped drinks ",drink_list)
drinks_to_caffeine = {key: value for key, value in drink_list}
print("Drinks to caffeine dictionary ",drinks_to_caffeine)

# Output:
Zipped drinks  [('espresso', 64), ('chai', 40), ('chai', 0), ('drip', 120)]
Drinks to caffeine dictionary  {'espresso': 64, 'chai': 0, 'drip': 120}
6 Likes

Hi

How would we know id a defined object is an iterator?
Zipped_drinks was defined to hold value from 2 lists. Shouldn’t such an object be usable any number of times in its scope?

  • As per your explanation, printing out zipped_drinks twice does confirm your answer.
    But how do we identify such objects in the code.
    On first look it didn’t seem like a iterator ( more so if I don’t intend to use in in loops )

  • Does this apply to range objects as well?

  • Why is this so in Python?

print("Zipped drinks ",list(zipped_drinks))
print("Zipped drinks ",list(zipped_drinks))

output
Zipped drinks  [('espresso', 64), ('chai', 40), ('decaf', 0), ('drip', 120)]
Zipped drinks  []

The most commonly used functions in Python 3 that return iterators (which is what we are discussing here) are: zip(), enumerate(), reversed(), map(), filter(). It seems to be the trend to return iterators, as some of those returned lists in Python 2; be on the lookout for more in future versions. I don’t know of any way to recognize which functions return iterators, except of course, by reading the docs or other instructions on their use.

Technically, range() is not an iterator. range can be “examined” in ways that iterators cannot:

my_range = range(5)
print(len(my_range))
print(my_range[3])
print(3 in my_range)
print(list(my_range))

Output:

5
3
True
[0, 1, 2, 3, 4]

Now let’s try it with a true iterator, say, the output of the enumerate() function:

my_list = ['a','b','c']
my_enumerate = enumerate(my_list)
#print(len(my_enumerate))  # raises error
#print(my_enumerate[2])  # raises error
print((1, 'b') in my_enumerate)
print(list(my_enumerate))

Output:

True        #  yes, (1, 'b') is in my_enumerate ...
[(2, 'c')]         # ... but discovering this partially exhausted the iterator!

I’m not sure - I’ve never seen an answer to this. Presumably, the iter and next functions that define iterators could be rewritten in such a way as to permit reuse.

My own theory of the reason is that we would like functions to return the same output from a given input, but an iterator can be used to change the iterable which generated it.

my_list = ['a','b','c']
my_enumerate = enumerate(my_list)
for idx, element in my_enumerate:
    my_list[idx] = element + 'x'
print(my_list)

Output:
[‘ax’, ‘bx’, ‘cx’]

So, at this point, what should my_enumerate look like if it could be re-used???


There is a lot about this on the internet. There is one thread on Stack Overflow that maintains that it is only the iterator type called “generator” that is exhaustible. That, at least, is not true.
Here are some good references.
The Iterator Protocol: How “For Loops” Work in Python
Iterables, iterators and generators, oh my! Part 1
Python: range is not an iterator!

3 Likes

My code:

drinks = ["espresso", "chai", "decaf", "drip"]
caffeine = [64, 40, 0, 120]
zipped_drinks = zip(drinks, caffeine)
print(tuple(zipped_drinks))
drinks_to_caffeine = {drinks:caffeine for drinks, caffeine in zipped_drinks}
print(drinks_to_caffeine)
>>>
(('espresso', 64), ('chai', 40), ('decaf', 0), ('drip', 120))
{}

Why is the dictionary empty when I print it? My response was correct according to the site, but did I not actually make a dictionary?

This prints fine…

drinks = ["espresso", "chai", "decaf", "drip"]
caffeine = [64, 40, 0, 120]
zipped_drinks = zip(drinks, caffeine)
#print(tuple(zipped_drinks))
drinks_to_caffeine = {drinks:caffeine for drinks, caffeine in zipped_drinks}
print(drinks_to_caffeine)

I have a similar question as above!
I think the question is that why are we unable to print both the ‘zipped_drinks’ list and the ‘drinks_to_caffeine’ after list comprehension?

image

if I shift both prints to the end of the code, the ‘zipped_drinks’ list appears to be the one that is empty this time round.
Thank you for your time!

Tried it myself and saw the same thing. I did some research and it appears that you can only unpack a zip object once. Once it has been iterated, it is exhausted.

I guess making it into a list when printing it is using the zip object once. Then making it into a dictionary is attempting to use it twice. A site suggested making a separate variable that creates a list of a zip object and I tried that with this lesson and it worked, just a little extra work to see the printed list version of the zip.

This extra work isn’t necessary for the lesson, but helpful to see what your code is doing.

drinks = ["espresso", "chai", "decaf", "drip"]
caffeine = [64, 40, 0, 120]


zipped_drinks = zip(drinks, caffeine)
zipped_drinks_list = list(zip(drinks,caffeine)) #creating a separate variable that is a list version of the zip object

drinks_to_caffeine = {key:value for key, value in zipped_drinks}

print(list(zipped_drinks_list)) #print this new variable so that I don't exhaust the zipped_drinks object
print(drinks_to_caffeine)

If you Google “zip list be unpacked once” you’ll see a couple of helpful more in depth explanations.

2 Likes

There are too many steps here. Using the zip(), you can combine the 2 lists right into a dictionary. Using tuple and zipping more than once isn’t necessary. I am fairly new to python, but not programming, so what I gather Is that this is the fastest, most efficient way. And it works fine!

drinks = ["espresso", "chai", "decaf", "drip"]
caffeine = [64, 40, 0, 120]
drinks_to_caffeine = {drinks:caffeine for drinks, caffeine in zip(drinks, caffeine)}
print(drinks_to_caffeine)

It is only zipped once, but stored in a variable. Abstraction is not something that is easy to teach to a beginner so most lessons make use of intermediary or temporary variables to show that step in a more verbose manner.

Ah, I understand. I haven’t come across tuples yet so I didn’t understand them fully, but soon as I saw the code, I knew I could remove that and the variable and go straight to the dictionary comprehension. Which way is preferred in a real world setting?

I’m no expert on the real world setting and see nothing wrong with your code. In this setting we need to write code that is easy to read and follow, without abstraction in the early stages. Think ‘naive’ over elegant or intuitive. Being a programmer it will seem like a step back, but it can’t hurt to get a refresher and see things from a beginner’s view.

For sure! I sometimes go the naive way when trying to get a function down and then edit for specifics and efficiency later on.

1 Like

I used this construction
drinks = [“espresso”, “chai”, “decaf”, “drip”]
caffeine = [64, 40, 0, 120]
drinks_to_caffeine = dict(zip(drinks, caffeine))
print(drinks_to_caffeine)
and it was right answer. Can I use it or I should to use constructions with “for”?

There is nothing wrong with using the dict() constructor, so long as other methods are apparent. It really depends what the exercise is expecting, but given freedom to choose our approach, any that work are fine.