Why does creating a list of lists produce unexpected behavior?
When you do the following:
[[]]*n
You are first creating a list, then using the *
operator with an int
n
. This takes whatever objects are in your list, and creates n- many repetitions of it.
But since in Python, explicit is better than implicit, you don't implicitly make a copy of those objects. Indeed, this is consistent with the semantics of Python.
Try to name a single case where Python implicitly makes a copy.
Furthermore, it is consistent with the addition on the list:
l = [1, [], 'a']
l2 = l + l + l
l[1].append('foo')
print(l2)
And the output:
[1, ['foo'], 'a', 1, ['foo'], 'a', 1, ['foo'], 'a']
Now, as noted in the comments, coming from C++ it makes sense that the above would be surprising, but if one is used to Python, the above is what one would expect.
On the other hand:
[[] for _ in range(5)]
Is a list comprehension. It is equivalent to:
lst = []
for _ in range(5):
lst.append([])
Here, clearly, every time you are in the loop you create a new list. That is how literal syntax works.
As an aside, I almost never use the *
operator on lists, except for one particular idiom I am fond of:
>>> x = list(range(1, 22))
>>> it_by_three = [iter(x)]*3
>>> for a,b,c in zip(*it_by_three):
... print(a, b, c)
...
1 2 3
4 5 6
7 8 9
10 11 12
13 14 15
16 17 18
19 20 21
Why does Python list of lists behave differently depending on their declaration?
It's because of the first line:
>>> a = b = []
>>> a
[]
>>> b
[]
>>> a is b
True
>>> a = []
>>> b = []
>>> a is b
False
>>>
With one line as in case 1, it contains the same object, so:
>>> a = b = []
>>> a.append(1)
>>> a
[1]
>>> b
[1]
>>>
Which doesn't happen with two lines:
>>> a = []
>>> b = []
>>> a.append(1)
>>> a
[1]
>>> b
[]
>>>
So simply because the first line of case 1 has a
and b
that are the exact same objects, unlike the second case's fist line, that are same values, but different id
(id(a) == id(b)
is the same as a is b
).
Why does the 'is' operator behave unexpectedly with arithmetically equal expressions
When you do something like :
(case-1)
a = 1000
b = a
or (case-2)
a = 1000
b = 1000
Python is smart enough to know before hand that even after execution you won't need new memory.
So, python just before execution makes b
an alias of a
in the first case.
The second case is bit different.
Python is a true object oriented language, the literal 1000
is treated as an object. (Intuitively you can think as 1000 to be name of a const object).
So in second case a
and b
are technically, both becoming alias
of 1000
Now in your example:
a = 1000
b = 1000 + a - a
print (a == b)
print (a is b)
while assignment of b
, python doesn't know before hand what is going to be the value of a
. When I say before-hand I mean before any form of calculation being started. So python reserves a new memory location for b
and then saves the output of the operation in this new memory location.
It is also worth noting this:
4-1 is 3
True
In this case, python doesn't saves this line with 4-1
but processes it before compilation to be 3
, for runtime optimisation.
Weird behavior in python list concatenation
The reason behind this is that +=
and +
calls two different methods of the class, __iadd__
method and __add__
method.
From an API perspective, iadd is supposed to be used for modifying mutable objects in place (returning the object which was mutated) whereas add should return a new instance of something. For immutable objects, both methods return a new instance, but iadd will put the new instance in the current namespace with the same name that the old instance had. This is why
i = 1
i += 1
seems to increment i. In reality, you get a new integer and assign it "on top of" i -- losing one reference to the old integer. In this case, i += 1 is exactly the same as i = i + 1. But, with most mutable objects, it's a different story:
As a concrete example:
a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a #[1, 2, 3, 1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
compared to:
a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
notice how in the first example, since b and a reference the same object, when I use += on b, it actually changes b (and a sees that change too -- After all, it's referencing the same list). In the second case however, when I do b = b + [1, 2, 3], this takes the list that b is referencing and concatenates it with a new list [1, 2, 3]. It then stores the concatenated list in the current namespace as b -- With no regard for what b was the line before.
Unexpected list behavior in Python
The following:
list_reversed = list
makes the two variables refer to the same list. When you change one, they both change.
To make a copy, use
list_reversed = list[:]
Better still, use the builtin function instead of writing your own:
list_reversed = reversed(list)
P.S. I'd recommend against using list
as a variable name, since it shadows the builtin.
Related Topics
How to Serve Static Files in Flask
Of the Many Findelement(S)/By Functions in Selenium, When Would You Use One Over the Other
Pip' Is Not Recognized as an Internal or External Command
How to Flush the Output of the Print Function
How to Force Division to Be Floating Point? Division Keeps Rounding Down to 0
Why Isn't the 'Global' Keyword Needed to Access a Global Variable
Does Python Have a String 'Contains' Substring Method
How to Add to the Pythonpath in Windows, So It Finds My Modules/Packages
Syntax Error on Print With Python 3
How to Use Threading in Python
Extracting Text from HTML File Using Python
Execute Python Script Via Crontab