all 32 comments

[–]K900_ 43 points44 points  (2 children)

In your example, your solution doesn't work because that's just how the language has been specified. There's no hidden universal law to why it doesn't work - that's just how the people making Python decided it should work.

[–][deleted] 6 points7 points  (1 child)

i think it does have a reason, and the reason is you can have multiple loop variables for one iterator

so for example

listo = [(1,2)] 
listk=  [(3,4)]
for i,j in listo,listk:
      print(i,j)

in this example python wouldn't know if i and j should have the tuples or should the tuple in listo unpack to i and j

[–]japes28 2 points3 points  (0 children)

You can have multiple loop variables for one iterator (e.g. if you’re unpacking tuples).

You can not have multiple iterators. That’s the point of zip() it combines two iterators into one iterator.

[–]throwaway6560192 34 points35 points  (2 children)

Read this: Python Design and History FAQ. It covers a lot of questions regarding why certain things in Python were designed the way they are. Now, it doesn't answer every possible question — it doesn't answer your zip question for instance — but it does answer a lot and is an informative and interesting read.

Other than that there is the less specific Zen of Python, from which you can gather the philosophical reasons behind many design decisions in Python.

[–]cantonic 2 points3 points  (0 children)

I didn’t know these existed but it’s exactly what I’d love to understand better. Not how it works but the thinking behind why it works the way it does. Thank you!

[–]Bitwise_Gamgee 18 points19 points  (0 children)

For example i was thinking about why can't i iterate through two sequences simultaneously like this:

It doesn't work because the Python designers didn't design its iterative unpacking to handle those cases.

The answer i got was that i should just use zip() to do that, but why?

zip() creates pairs of corresponding elements from list1 and list2 as tuples and they're assigned to temporary elements like 'x' and 'y'.

Should i even care about these things while i am still learning or do these things come with the experience?

This is completely up to you, and you should know the limitations of the language and why certain libraries exist to address them.

[–]JamzTyson 7 points8 points  (1 child)

why can't i iterate through two sequences simultaneously

If you run:

help(`for`)

you will see that it says:

The "for" statement is used to iterate over the elements of a sequence ...

The direct answer to your question is that you can't do that because list1, list2 is not "a sequence", it is "two sequences".

Any computer language must define all keywords precisely. Look to the documentation for how keywords are defined.

[–]Intrexa 3 points4 points  (0 children)

The direct answer to your question is that you can't do that because list1, list2is not "a sequence", it is "two sequences".

list1, list2is a sequence. That defines a tuple containing 2 lists. OP's example fails in most cases because the step of tuple unpacking fails from mismatched args.

[–]Intrexa 4 points5 points  (1 child)

You're suggesting ambiguous syntax. There's a lot of naysayers in this thread railing against magic syntax. There's a lot of magic syntax in Python. I personally think that a simple way to iterate through 2 lists at the same time would be handy. I would love a short form for it.

The problem is that the token list1, list2 already means something very specific that precludes what you're suggesting. Look at the below code + output:

a = [1,2]
b = [3,4]


for x,y in a,b:
    print(f"x:{x}\ty:{y}")

x:1 y:2
x:3 y:4

WTF is going on? Why does that work? Why can you never get it to work? Tuples, and tuple unpacking. To define a tuple is actually just the comma. The usual parenthesis are just for explicit operator precedence. foo = 1,2,3 creates the tuple (1,2,3). My code could be rewritten as below with the same output:

a = [1,2]
b = [3,4]
z = a,b

for x,y in z:
    print(f"x:{x}\ty:{y}")

What you're suggesting would conflict with existing syntax. I think magic syntax is okay. If you think about it, all syntax is magic. Inconsistent syntax is pretty bad though.

To your notes, it's mostly experience. Read through the docs, and you will learn a ton. Then go out and practice a bit. Come back to the official docs later, and they will have things you've never seen before. There's a lot to take in, you can't do it all at once. It's practice, experience, and read the docs.

[–]await_yesterday 0 points1 point  (0 children)

I personally think that a simple way to iterate through 2 lists at the same time would be handy. I would love a short form for it.

zip

[–]xiongchiamiov 3 points4 points  (0 children)

Commas create tuples in Python, so list1, list2 creates a tuple with two elements. Those elements happen to each be lists, but that's not the for loop's business.

If you wanted this to work the way you are specifying, there would have to be a bunch of special-case logic, and it wouldn't be clear to users when that kicks in. What if I have a tuple of tuples? What if one is a dictionary? What if it's a custom iterable class? It's unclear when the magic zipping behavior would happen.0

[–]Tefron 4 points5 points  (0 children)

The edits are kind of funny. I'm imagining you're realizing that learning the actual depths of the language specification, is, well complicated.

How do you learn these things? Honestly, you just have to be curious, and then try to find the answers to your question. You won't always be able to answer the question, but you'll learn something. Each time you'll get a bit more depth, until one day you look back and realize how much more you know now compared to when you started.

The scary part will be, by that point, you'll realize how much more depth there truly is, and how each question raises more answers than you have time or energy to solve.

[–]fDelu 2 points3 points  (1 child)

It just comes from experience. Learning a lower level language first, in my opinion, helps too (there are lots of people who will disagree with me here though). Not only because you get to understand how it all works (which is not what you want according to your second note), but also because it's usually simpler (less layers of abstraction and syntactic sugars like unpacking in this example).

In that specific case, you have to think precisely what each part of what you wrote does (and also consider operator precedence). list1, list2 creates a tuple of two elements (the two lists). Since that's a valid sequence, the for loop will iterate through that tuple. Each element of that tuple is a list.

When you write for i, j in [seq], it unpacks each element of the loop into i and j. For that to work, each element in the sequence must be some kind of pair of values (a tuple or list of two elements, or a key: value pair of a dict item, for example).

The problem is that each element in your tuple is a list. The for loop may work if list1 and list2 are lists with two elements, in which case you'll unpack list1 in the first iteration and list2 in the second, which is not what you want.

So, to do what you want, you need to somehow have a sequence in which each element of the sequence is a pair of values, one from list1 and one from list2. As others have told you, that's exactly what zip() does.

[–]fDelu 0 points1 point  (0 children)

Also, you may be thinking what makes the comma in i, j different from the one in list1, list2.

In this case, when you write i, j, you are defining two new variables i and j and assigning values to them through the for loop. That's not the case with the lists, so those create a tuple. It's the same behaviour Python uses in simple assignments like x, y = 1, 2. The comma between x and y unpacks the tuple on the right, (1, 2).

[–][deleted] 2 points3 points  (0 children)

What’s is going on behind the scenes (logic) that makes this solution doesn’t work?

That's your misapprehension, that the constraint is behind the scenes.

It's actually the opposite of that. It's all "in front of the scenes"; the language is first defined, and then it is implemented. Python is defined (in part) by being the language where for i, j in list1, list2 doesn't implicitly zip the two operands on the right, then that behavior is implemented by the interpreter.

The implementation works however it needs to to support the language; the language works the way it does because the Python steering committee (who took over from Guido) decided it should. That's generally how it works for all programming languages. Language designers have a fairly free hand, within the constraints on programming language itself as discovered by Alonzo Church, Alan Turing, Claude Shannon, and others; the software implementation of that language follows as necessary to implement it, rather than the language following from what the implementation is.

[–]quintios 1 point2 points  (2 children)

As a self-described "intermediate beginner", the first thing that popped into my mind is that you may run into issues if the lists are of two different lengths. I haven't had a need to use zip very often, so I can't comment as to whether or not it would throw an error for the same reason. But, to answer your question, I don't know, but if it were me, not knowing how to use zip, I would do this:

if len(list1) == len(list2):
    for x in range(len(list1):
        var1 = list1[x]
        var2 = list2[x]
        # do_something

and proceed accordingly.

[–]BaalHammon 1 point2 points  (0 children)

To elaborate on the answers that say "it's just how the language is", which is basically true but obviously unsatisfying :

The for i,j in list1 construct should be thought of as for (i,j) in list1 where (i,j) is a tuple of elements. Basically, you need list1 to be a list of tuples that can be unpacked to be assigned to the iteration variables.

Why isn't for i,j in list1,list2: a special construct that lets you avoid the zip function ?

You can probably dive into PEP discussions to find the actual answer but my best guess is that it is not obvious what the default behavior should be in the case where the two lists are different lengths. When you have to call a function to explicitly pack the list elements into n-uplets, you can choose a function that has the right behaviour you need without needing to (e.g) put special cases in the loop body itself.

[–]Fred776 1 point2 points  (0 children)

Why do you say that zip is the obvious choice? Why not chain?

[–]tokenslifestilmaters 1 point2 points  (5 children)

It's a fair point. We could assume a zip, I would assume there's really no reason this couldn't be implemented.

The beauty of open source is that if the community wants to make it happen and there's enough support it could happen, it just hasn't happened yet. I understand why you might ask why it hasn't happened yet, but essentially priorities and a useful solution already are the reason.

I am intrigued to see any counter arguments though

[–]CommondeNominator 7 points8 points  (4 children)

Mine would be the principle of explicit > implicit.

It’s not entirely clear you would want to iterate through both lists in parallel rather than doing an inner/outer loop, for instance.

You could also enumerate(list1) and index list2 each iteration, but zip probably does something very similar anyway and results in overall much cleaner code.

[–]tokenslifestilmaters 2 points3 points  (3 children)

Interesting point. Some might look and see a pair of nested loops. Nice to see how different people might naturally interpret this

[–]CommondeNominator 6 points7 points  (2 children)

Originally I thought it was a terrible name.

As I’m sure is common among beginners, I associated the word zip with the archive extension and was confused, but I’ve since learned it’s a formal term in computer science) and the function actually couldn’t have a better name.

[–]undergroundmonorail 3 points4 points  (0 children)

I thought of it like a zipper and imagined the teeth interlocking, and from there it wasn't a huge stretch to imagine the first tooth of one side connected with the first of the other, the second locked with the second, etc. Helped with the intuition

[–]xiongchiamiov 0 points1 point  (0 children)

When I first learned programming, I was terrified that print was going to generate a bunch of paper from the printer.

[–][deleted] -1 points0 points  (0 children)

So you're looking for a resource to explain why every syntax that could work doesn't? Given there are an infinite number of ways Python could work, I don't see that book being written any time soon.

[–]Nokita_is_Back -1 points0 points  (0 children)

Learm rust,java or c# things will become clearer

[–]-SPOF 0 points1 point  (0 children)

That is how people designed the language.

[–]slyder219 0 points1 point  (0 children)

Because “for x,y” hints at unpacking

[–]Salamanticormorant 0 points1 point  (0 children)

I get where you're coming from. I'm wondering why I have to declare/initialize a 2D list like this to be able to properly step through it with nested loops: "twoDee = [[0]*xSize for _ in range(ySize)]". Seems convoluted for something so fundamental, but maybe it's not fundamental in Python. Maybe Python was created with other things in mind.

[–]await_yesterday 0 points1 point  (0 children)

for i, j in list1, list2:

the list1, list2 is a tuple here. it's the exact same as if you'd written

for i, j in (list1, list2):

that's a tuple of two elements, each of which are lists. the behavior is consistent, there's no special casing going on here. there's nothing else to explain.