Unpacking a list of lists in a for loop - python

Let's say I create a list in the following way:
a = [[0, 1], [2, 3]]
If I execute the following for loop:
for b in a:
print(b)
I get
[0, 1]
[2, 3]
which makes sense to me because I am looking for elements of a which, in that case, are sub-lists. However, if I execute:
for b, c in a:
print(b, c)
I get
0 1
2 3
I would have expected to get
[0, 1] [2, 3]
Is there an obvious reason why Python is unpacking the list this way? I mean, why Python would suddenly give me elements of sub-elements of my list when I am requesting objects in the list?

You're unpacking to the integers here:
>>> for b, c in a:
... print(b, c)
... print(type(b), type(c))
...
0 1
<class 'int'> <class 'int'>
2 3
<class 'int'> <class 'int'>
Put another way, compare the following two:
>>> b, c = a; print(b, c)
[0, 1] [2, 3]
>>> b, c = a[0]; print(b,c)
0 1

When you specified:
for b, c in a:
print(b, c)
You could read that as: for each element in a, unpack into the tuple "b, c"

It's helpful to understand the Assignment Statement being used here. Relevant part here:
If the target list is a single target with no trailing comma,
optionally in parentheses, the object is assigned to that target.
Else: The object must be an iterable with the same number of items as
there are targets in the target list, and the items are assigned, from
left to right, to the corresponding targets.
The for loop essentially boils down to this:
for (object) in (iterable):
Now let's see what objects are in your iterable:
[0, 1] # object1: list
[2, 3] # object2: list
But when you use the Assignment Statement, the "Else" clause applies.
for (obj1, obj2 of object) in (iterable):
Which treats the object as an inner iterable, and unpack each to the inner objects, i.e. the ints within:
0 1 # obj1 of object1: int, obj2 of object1: int
2 3 # obj1 of object2: int, obj2 of object2: int

What you're experiencing is unpacking assignment, which assigns the elements of an iterable to multiple variables. To get your expected behavior, do the following:
b, c = a
print(b, c)
for b, c in a: iterates through a the same way as for b in a:, but since each element is a 2-element iterable, its own elements get unpacked to b and c. See what happens when you do this:
a = [[0, 1], [2, 3, 4]]
for b, c in a:
print(b, c)
You would get an error, because a[1] has more elements than there are variables to assign to.

Related

Why does * work differently in assignment statements versus function calls?

In Python, varargs collection seems to work quite differently from how sequence unpacking works in assignment statements. I'm trying to understand the reason for this potentially confusing difference. I'm sure there is a good reason, but what is it?
# Example 1 with assignment statement
a, *b, c = 1,2,3,4
print(b) # [2, 3]
# Example 1 with function call
def foo(a, *b, c):
print(b)
foo(1,2,3,4)
The function call results in the following error:
Traceback (most recent call last):
File "<pyshell#309>", line 1, in <module>
foo(1,2,3,4)
TypeError: foo() missing 1 required keyword-only argument: 'c'
Question 1: Why is b not assigned similar to how it is done in the assignment statement?
# Example 2 with function call
def foo(*x):
print(x)
print(type(x))
foo(1,2,3) # (1, 2, 3) <class 'tuple'>
# Example 2 with assignment statement
a, *b, c = 1,2,3,4,5
print(b) # [2, 3, 4]
print(type(b)) # <class 'list'>
Question 2: Why the difference in type (list vs tuple)?
When used in an assignment, Python will try to make *b match to whatever it needs in order to make the assignment work (this is new in Python 3, see PEP 3132). These are both valid:
a, *b, c = 1,4
print(b) # []
a, *b, c = 1,2,3,4,5
print(b) # [2, 3, 4]
When used in a function, if *b is the second parameter in the function definition, it will match with the second to last arguments in the function call, if there are any. It's used when you want your function to accept a variable number of parameters. Some examples:
def foo(a, *b):
print(b)
foo(1) # ()
foo(1,2,3,4,5) # (2,3,4,5)
I recommend you read the following:
PEP 3132 -- Extended Iterable Unpacking
Understanding the asterisk(*) of Python.
Unpacking Argument Lists.
Keyword Arguments
On the difference between lists and tuples, the big one is mutability. Lists are mutable, tuples aren't. That means that this works:
myList = [1, 2, 3]
myList[1] = 4
print(myList) # [1, 4, 3]
And this doesn't:
myTuple = (1, 2, 3)
myTuple[1] = 4 # TypeError: 'tuple' object does not support item assignment
The reason why b is a list in this case:
a, *b, c = 1,2,3,4,5
print(b) # [2, 3, 4]
And not a tuple (as is the case when using *args in a function), is because you'll probably want to do something with b after the assignment, so it's better to make it a list since lists are mutable. Making it a tuple instead of a list is one of the possible changes that were considered before this was accepted as a feature, as discussed in PEP 3132:
Make the starred target a tuple instead of a list. This would be
consistent with a function's *args, but make further processing of the
result harder.

Changing values of tuple elements

I entered the following at the command line:
>>>a = 25
>>>b = 50
>>>t = (a, b)
>>>t
(25, 50)
>>>a = 50
>>>t
(25, 50)
>>>t = (a, b)
>>>t
(50, 50)
Why do I have to reassign the tuple (a, b) to t to see the change in a's value?
This has less to do with tuples and more to do with how assignment works in Python which copies vs references.
This works for other container types (lists) and plain variables.
>>> a = 2
>>> b = [1, a]
>>> a = 7
>>> b
[1, 2]
>>> c = 1
>>> d = c
>>> c = 2
>>> d
1
At first, the value in tuple in python can not be changed. You can only declare a new tuple.
a = 25 means a is a variable. However, the a in t belong to the tuple t. It does not have any relationship with variable a.
The second t = (a, b) as same as t = (50, 50)
Furthermore, you can use id(a) and id(t) to see the difference in your memory address.
You can understand it in this way:-
>>> a =25
>>> b = 50
>>> id(a)
6070712
>>> t = (a, b)
>>> id(t[0])
6070712
>>> a = 50
>>> id(a)
6070412
# When you assigned a = 50, it created new object,
#id(t[0]) and a is not same now, so t is still (25, 50)
This happened because int is immutable, every time you assign a value to it, new object would be created.
Repeat same with mutable type(which can be modified in place)
>>> ls1 = [1,2]
>>> ls2 = [3,4]
>>> t = (ls1, ls2)
>>> ls1[0] = 23
>>> t
([23, 2], [3, 4])
>>> id(ls1)
54696136
>>> id(t[0])
54696136
#here t[0] and ls1 are same object because we could modify ls1 in place
I hope it would help.
It is simple. In Python, the names, such as a and b and t are not objects, they just point to objects. When you enter
>>> a = 25
>>> b = 50
Python sets the name a to point to an int object with value 25 and b to point to int object with value 50.
when you create a tuple with
>>> t = a, b
(no parenthesis required here!) you're telling Python that "please make a new tuple of 2 elements, the first position of which should point to the object that a now points to and the second position should point to the object that b now points to. Actually it would work similarly with a list, or set as well:
>>> l = [a, b]
>>> s = {a, b}
Now the next statement:
>>> a = 50
Means "now, set a to point to an int object with value of 50". The first element of the tuple still continues to point to 25. Actually all assignments to variables behave this way in Python, be the value in a mutable or not:
>>> a = [1, 2]
>>> b = [3, 4]
>>> t = a, b
>>> a = [5, 6]
>>> t
([1, 2], [3, 4])
Even though a points to a mutable value, a list, then a, b means *make a new tuple with first element being the object that a points to at this very moment, and second element being the object that b points to at this very moment; and then a = [5, 6] means *create a new list ... and now make a point to it`. The variables (names) in Python indeed are not "boxes", but they're sort of signs that point to the values.
If we do an assign action like a = b and b is immutable(number, string, tuple etc),assign will make a copy rather than make reference. As a result, modify to the b do not effect a.
For mutable situation:
>>> a = []
>>> b = 3
>>> c = (a, b)
>>> c
([], 3)
>>> a.append(1)
>>> c
((1], 3)

Extended sequence unpacking in python3

I create a list as:
>>> seq = [1, 2, 3, 4]
Now i assign the variables as follows:
>>> a, b, c, d, *e = seq
>>> print(a, b, c, d, e)
I get output as:
>>> 1 2 3 4 []
Now i change the sequence in which i assign variables as:
>>> a, b, *e, c, d = seq
>>> print(a, b, c, d, e)
I get output as:
>>> 1, 2, 3, 4, []
So my question is Why *e variable above is always assigned an empty list regardless of where it appears?
It was a design choice, according to PEP 3132 (with my bold):
A tuple (or list) on the left side of a simple assignment (unpacking is not defined for augmented assignment) may contain at most one expression prepended with a single asterisk (which is henceforth called a "starred" expression, while the other expressions in the list are called "mandatory"). This designates a subexpression that will be assigned a list of all items from the iterable being unpacked that are not assigned to any of the mandatory expressions, or an empty list if there are no such items.
Indeed, the first example in the PEP illustrates your point:
>>> a, *b, c = range(5)
>>> a
0
>>> c
4
>>> b
[1, 2, 3]
In the first case
a, b, c, d, *e = seq
since the sequence has only four elements, a, b, c and d get all of them and the rest of them will be stored in e. As nothing is left in the sequence, e is an empty list.
In the second case,
a, b, *e, c, d = seq
First two elements will be assigned to a and b respectively. But then we have two elements after the *e. So, the last two elements will be assigned to them. Now, there is nothing left in the seq. That is why it again gets an empty list.
a, b, *e, c, d = [1, 2, 3, 4]
*e says "what's left put into e". Because you have 4 elements, empty sequence is put into e.

Python assigning multiple variables to same value? list behavior

I tried to use multiple assignment as show below to initialize variables, but I got confused by the behavior, I expect to reassign the values list separately, I mean b[0] and c[0] equal 0 as before.
a=b=c=[0,3,5]
a[0]=1
print(a)
print(b)
print(c)
Result is:
[1, 3, 5]
[1, 3, 5]
[1, 3, 5]
Is that correct? what should I use for multiple assignment?
what is different from this?
d=e=f=3
e=4
print('f:',f)
print('e:',e)
result:
('f:', 3)
('e:', 4)
If you're coming to Python from a language in the C/Java/etc. family, it may help you to stop thinking about a as a "variable", and start thinking of it as a "name".
a, b, and c aren't different variables with equal values; they're different names for the same identical value. Variables have types, identities, addresses, and all kinds of stuff like that.
Names don't have any of that. Values do, of course, and you can have lots of names for the same value.
If you give Notorious B.I.G. a hot dog,* Biggie Smalls and Chris Wallace have a hot dog. If you change the first element of a to 1, the first elements of b and c are 1.
If you want to know if two names are naming the same object, use the is operator:
>>> a=b=c=[0,3,5]
>>> a is b
True
You then ask:
what is different from this?
d=e=f=3
e=4
print('f:',f)
print('e:',e)
Here, you're rebinding the name e to the value 4. That doesn't affect the names d and f in any way.
In your previous version, you were assigning to a[0], not to a. So, from the point of view of a[0], you're rebinding a[0], but from the point of view of a, you're changing it in-place.
You can use the id function, which gives you some unique number representing the identity of an object, to see exactly which object is which even when is can't help:
>>> a=b=c=[0,3,5]
>>> id(a)
4473392520
>>> id(b)
4473392520
>>> id(a[0])
4297261120
>>> id(b[0])
4297261120
>>> a[0] = 1
>>> id(a)
4473392520
>>> id(b)
4473392520
>>> id(a[0])
4297261216
>>> id(b[0])
4297261216
Notice that a[0] has changed from 4297261120 to 4297261216—it's now a name for a different value. And b[0] is also now a name for that same new value. That's because a and b are still naming the same object.
Under the covers, a[0]=1 is actually calling a method on the list object. (It's equivalent to a.__setitem__(0, 1).) So, it's not really rebinding anything at all. It's like calling my_object.set_something(1). Sure, likely the object is rebinding an instance attribute in order to implement this method, but that's not what's important; what's important is that you're not assigning anything, you're just mutating the object. And it's the same with a[0]=1.
user570826 asked:
What if we have, a = b = c = 10
That's exactly the same situation as a = b = c = [1, 2, 3]: you have three names for the same value.
But in this case, the value is an int, and ints are immutable. In either case, you can rebind a to a different value (e.g., a = "Now I'm a string!"), but the won't affect the original value, which b and c will still be names for. The difference is that with a list, you can change the value [1, 2, 3] into [1, 2, 3, 4] by doing, e.g., a.append(4); since that's actually changing the value that b and c are names for, b will now b [1, 2, 3, 4]. There's no way to change the value 10 into anything else. 10 is 10 forever, just like Claudia the vampire is 5 forever (at least until she's replaced by Kirsten Dunst).
* Warning: Do not give Notorious B.I.G. a hot dog. Gangsta rap zombies should never be fed after midnight.
Cough cough
>>> a,b,c = (1,2,3)
>>> a
1
>>> b
2
>>> c
3
>>> a,b,c = ({'test':'a'},{'test':'b'},{'test':'c'})
>>> a
{'test': 'a'}
>>> b
{'test': 'b'}
>>> c
{'test': 'c'}
>>>
In python, everything is an object, also "simple" variables types (int, float, etc..).
When you changes a variable value, you actually changes it's pointer, and if you compares between two variables it's compares their pointers.
(To be clear, pointer is the address in physical computer memory where a variable is stored).
As a result, when you changes an inner variable value, you changes it's value in the memory and it's affects all the variables that point to this address.
For your example, when you do:
a = b = 5
This means that a and b points to the same address in memory that contains the value 5, but when you do:
a = 6
It's not affect b because a is now points to another memory location that contains 6 and b still points to the memory address that contains 5.
But, when you do:
a = b = [1,2,3]
a and b, again, points to the same location but the difference is that if you change the one of the list values:
a[0] = 2
It's changes the value of the memory that a is points on, but a is still points to the same address as b, and as a result, b changes as well.
Yes, that's the expected behavior. a, b and c are all set as labels for the same list. If you want three different lists, you need to assign them individually. You can either repeat the explicit list, or use one of the numerous ways to copy a list:
b = a[:] # this does a shallow copy, which is good enough for this case
import copy
c = copy.deepcopy(a) # this does a deep copy, which matters if the list contains mutable objects
Assignment statements in Python do not copy objects - they bind the name to an object, and an object can have as many labels as you set. In your first edit, changing a[0], you're updating one element of the single list that a, b, and c all refer to. In your second, changing e, you're switching e to be a label for a different object (4 instead of 3).
You can use id(name) to check if two names represent the same object:
>>> a = b = c = [0, 3, 5]
>>> print(id(a), id(b), id(c))
46268488 46268488 46268488
Lists are mutable; it means you can change the value in place without creating a new object. However, it depends on how you change the value:
>>> a[0] = 1
>>> print(id(a), id(b), id(c))
46268488 46268488 46268488
>>> print(a, b, c)
[1, 3, 5] [1, 3, 5] [1, 3, 5]
If you assign a new list to a, then its id will change, so it won't affect b and c's values:
>>> a = [1, 8, 5]
>>> print(id(a), id(b), id(c))
139423880 46268488 46268488
>>> print(a, b, c)
[1, 8, 5] [1, 3, 5] [1, 3, 5]
Integers are immutable, so you cannot change the value without creating a new object:
>>> x = y = z = 1
>>> print(id(x), id(y), id(z))
507081216 507081216 507081216
>>> x = 2
>>> print(id(x), id(y), id(z))
507081248 507081216 507081216
>>> print(x, y, z)
2 1 1
in your first example a = b = c = [1, 2, 3] you are really saying:
'a' is the same as 'b', is the same as 'c' and they are all [1, 2, 3]
If you want to set 'a' equal to 1, 'b' equal to '2' and 'c' equal to 3, try this:
a, b, c = [1, 2, 3]
print(a)
--> 1
print(b)
--> 2
print(c)
--> 3
Hope this helps!
What you need is this:
a, b, c = [0,3,5] # Unpack the list, now a, b, and c are ints
a = 1 # `a` did equal 0, not [0,3,5]
print(a)
print(b)
print(c)
Simply put, in the first case, you are assigning multiple names to a list. Only one copy of list is created in memory and all names refer to that location. So changing the list using any of the names will actually modify the list in memory.
In the second case, multiple copies of same value are created in memory. So each copy is independent of one another.
The code that does what I need could be this:
# test
aux=[[0 for n in range(3)] for i in range(4)]
print('aux:',aux)
# initialization
a,b,c,d=[[0 for n in range(3)] for i in range(4)]
# changing values
a[0]=1
d[2]=5
print('a:',a)
print('b:',b)
print('c:',c)
print('d:',d)
Result:
('aux:', [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
('a:', [1, 0, 0])
('b:', [0, 0, 0])
('c:', [0, 0, 0])
('d:', [0, 0, 5])
To assign multiple variables same value I prefer list
a, b, c = [10]*3#multiplying 3 because we have 3 variables
print(a, type(a), b, type(b), c, type(c))
output:
10 <class 'int'> 10 <class 'int'> 10 <class 'int'>
Initialize multiple objects:
import datetime
time1, time2, time3 = [datetime.datetime.now()]*3
print(time1)
print(time2)
print(time3)
output:
2022-02-25 11:52:59.064487
2022-02-25 11:52:59.064487
2022-02-25 11:52:59.064487
E.g: basically a = b = 10 means both a and b are pointing to 10 in the memory, you can test by id(a) and id(b) which comes out exactly equal to a is b as True.
is matches the memory location but not its value, however == matches the value.
let's suppose, you want to update the value of a from 10 to 5, since the memory location was pointing to the same memory location you will experience the value of b will also be pointing to 5 because of the initial declaration.
The conclusion is to use this only if you know the consequences otherwise simply use , separated assignment like a, b = 10, 10 and won't face the above-explained consequences on updating any of the values because of different memory locations.
The behavior is correct. However, all the variables will share the same reference. Please note the behavior below:
>>> a = b = c = [0,1,2]
>>> a
[0, 1, 2]
>>> b
[0, 1, 2]
>>> c
[0, 1, 2]
>>> a[0]=1000
>>> a
[1000, 1, 2]
>>> b
[1000, 1, 2]
>>> c
[1000, 1, 2]
So, yes, it is different in the sense that if you assign a, b and c differently on a separate line, changing one will not change the others.
Here are two codes for you to choose one:
a = b = c = [0, 3, 5]
a = [1, 3, 5]
print(a)
print(b)
print(c)
or
a = b = c = [0, 3, 5]
a = [1] + a[1:]
print(a)
print(b)
print(c)

+ and += operators are different? [duplicate]

This question already has answers here:
Why does += behave unexpectedly on lists?
(9 answers)
Closed 9 years ago.
>>> c = [1, 2, 3]
>>> print(c, id(c))
[1, 2, 3] 43955984
>>> c += c
>>> print(c, id(c))
[1, 2, 3, 1, 2, 3] 43955984
>>> del c
>>> c = [1, 2, 3]
>>> print(c, id(c))
[1, 2, 3] 44023976
>>> c = c + c
>>> print(c, id(c))
[1, 2, 3, 1, 2, 3] 26564048
What's the difference? are += and + not supposed to be merely syntactic sugar?
docs explain it very well, I think:
__iadd__(), etc.
These methods are called to implement the augmented arithmetic assignments (+=, -=, *=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=). These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self). If a specific method is not defined, the augmented assignment falls back to the normal methods. For instance, to execute the statement x += y, where x is an instance of a class that has an __iadd__() method, x.__iadd__(y) is called.
+= are designed to implement in-place modification. in case of simple addition, new object created and it's labelled using already-used name (c).
Also, you'd notice that such behaviour of += operator only possible because of mutable nature of lists. Integers - an immutable type - won't produce the same result:
>>> c = 3
>>> print(c, id(c))
3 505389080
>>> c += c
>>> print(c, id(c))
6 505389128
They are not same
c += c append a copy of content of c to c itself
c = c + c create new object with c + c
For
foo = []
foo+=foo is syntactic sugar for foo.extend(foo) (and not foo = foo + foo)
In the first case, you're just appending members of a list into another (and not creating a new one).
The id changes in the second case because a new list is created by adding two lists. It's incidental that both are the same and the result is being bound to the same identifier than once pointed to them.
If you rephrase this question with different lists (and not c itself), it will probably become clearer.
The += operator appends the second list to the first, but the modification is in-place, so the ID remains the same.
When you use + , a new list is created, and the final "c" is a new list, so it has a different ID.
The end result is the same for both operations though.

Categories