Difference between list.pop() and list = list[:-1] - python

>>> a = [1,2,3]
>>> a.pop()
3
>>> a
[1, 2]
>>> a = [1,2,3]
>>> a = a[:-1]
>>> a
[1, 2]
>>>
Is there any difference between the above methods to remove the last element from a list?

Yes. pop is O(1) and mutates the original list, while slice is O(n) and creates a copy of the list. Less formally, the pop method is an operation on the element at the end of the list and is defined in CPython as a call to list_resize(self, Py_SIZE(self) - 1);. This doesn't traverse the entire structure.
On the other hand, list_slice allocates a new list and loops over the entries in the old list ranging from the beginning to the end - 1, copying references to each item to the new list.
If what you're trying to do is remove the last element of the list, use pop or del a[-1].

pop do not change the id, just pop one item of list.
[:-1] is slice operation, which create a new list from old list.
>>> a = [1,2,3]
print(id(a))
>>> a.pop()
3
print(id(a))
>>> a
[1, 2]
>>> a = [1,2,3]
>>> a = a[:-1]
>>> a
print(id(a))
[1, 2]
>>>
id output (the number is not important, same or not same is key point):
4470627464
4470627464
4474450952

the pop method returns the last item from the list that it removes.
for example:
a = [1,2,3,4]
b = a.pop()
print(b) # 4
Also, using slicing, you are making a copy of the old list, whereas with using pop the list reference remains the same.

There is an basic difference thats occurs using in functions . Using[:-1] unchanged the original list but pop() can do.
a = [1,2,3]
b = [1,2,3]
def functionb(list):
list = list[:-1]
return list
def withpop(list):
return list.pop()
functionb(b)
withpop(a)
print b
print a
Will printed:
[1, 2, 3]
[1, 2]
Second is execution time . pop() is faster than [:-1] Because when you use [:-1]you have to overwrite to list.Lets say you have thousands values in index so it will be slowly than pop()

The way you have presented them, there's no outward difference. The pop instruction gives the interpreter an easier time of optimizing the instruction, as it can merely decrement the length attribute of the list. The -1 assignment will construct a new list, assign that to a, and then leave the old one for garbage collection.
There is a huge difference in aliasing: if you assigned something else to that list, you will get side effects with pop. For instance:
>>> a = [1, 2, 3, 4]
>>> b = a
>>> b
[1, 2, 3, 4]
>>> a.pop()
4
>>> b
[1, 2, 3]
>>> a = a[:-1]
>>> b
[1, 2, 3]
>>> a
[1, 2]

Yes there is difference.
when you use a.pop() you remove from list too
when you use a[:-1] object list not change
check with len(a)
>>> a = [1,2,3]
>>> a
[1, 2, 3]
>>> a[:-1]
[1, 2]
>>> len(a[:-1])
2
>>> a.pop()
3
>>> a
[1, 2]
>>> len(a)
2
>>>

Related

Why does Python react differently on assigning matrix element depending on how you build the matrix? [duplicate]

I create a list of lists and want to append items to the individual lists, but when I try to append to one of the lists (a[0].append(2)), the item gets added to all lists.
a = []
b = [1]
a.append(b)
a.append(b)
a[0].append(2)
a[1].append(3)
print(a)
Gives: [[1, 2, 3], [1, 2, 3]]
Whereas I would expect: [[1, 2], [1, 3]]
Changing the way I construct the initial list of lists, making b a float instead of a list and putting the brackets inside .append(), gives me the desired output:
a = []
b = 1
a.append([b])
a.append([b])
a[0].append(2)
a[1].append(3)
print(a)
Gives: [[1, 2], [1, 3]]
But why? It is not intuitive that the result should be different. I know this has to do with there being multiple references to the same list, but I don't see where that is happening.
It is because the list contains references to objects. Your list doesn't contain [[1 2 3] [1 2 3]], it is [<reference to b> <reference to b>].
When you change the object (by appending something to b), you are changing the object itself, not the list that contains the object.
To get the effect you desire, your list a must contain copies of b rather than references to b. To copy a list you can use the range [:]. For example:
>>> a = []
>>> b = [1]
>>> a.append(b[:])
>>> a.append(b[:])
>>> a[0].append(2)
>>> a[1].append(3)
>>> print a
[[1, 2], [1, 3]]
The key is this part:
a.append(b)
a.append(b)
You are appending the same list twice, so both a[0] and a[1] are references to the same list.
In your second example, you are creating new lists each time you call append like a.append([b]), so they are separate objects that are initialized with the same float value.
In order to make a shallow copy of a list, the idiom is
a.append(b[:])
which when doubled will cause a to have two novel copies of the list b which will not give you the aliasing bug you report.

why mutable objects having same value have different id in Python

Thank you for your valuable time, I have just started learning Python. I came across Mutable and Immutable objects.
As far as I know mutable objects can be changed after their creation.
a = [1,2,3]
print(id(a))
45809352
a = [3,2,1]
print(id(a))
52402312
Then why id of the same list "a" gets changed when its values are changed.
your interpretation is incorrect.
When you assign a new list to a, you change its reference.
On the other hand you could do:
a[:] = [3,2,1]
and then the reference would not change.
mutable means that the content of the object is changed. for example a.append(4) actually make a equal to [1, 2, 3, 4], while on the contrary, appending to a string (which is immutable) does not change it, it creates a new one.
However, when you re-assign, you create a new object and assign it to a, you don't alter the existing content of a. The previous content is lost (unless refered-to by some other variable)
If you change a list, its id doesn't change. But you may do things that instead create a new list, and then it will also have a new id.
E.g.,
>>> l=[]
>>> id(l)
140228658969920
>>> l.append(3) # Changes l
>>> l
[3]
>>> id(l)
140228658969920 # Same ID
>>> l = l + [4] # Computes a new list that is the result of l + [4], assigns that
>>> l
[3, 4]
>>> id(l)
140228658977608 # ID changed
When you do
a = [3, 2, 1]
You unlink the list of [1, 2, 3] from variable a.
Create a new list [3, 2, 1] then assign it to a variable.
Being immutable doesn't mean you assign a new object, it means your original object can be changed "in place" for example via .append()
>>> my_list = [1,2,3]
>>> id(my_list)
140532335329544
>>> my_list.append(5)
>>> id(my_list)
140532335329544
>>> my_list[3] = 4
>>> my_list
[1, 2, 3, 4]
>>> id(my_list)
140532335329544

What's the difference between a[] and a[:] when assigning values?

I happen to see this snippet of code:
a = []
a = [a, a, None]
# makes a = [ [], [], None] when print
a = []
a[:] = [a, a, None]
# makes a = [ [...], [...], None] when print
It seems the a[:] assignment assigns a pointer but I can't find documents about that. So anyone could give me an explicit explanation?
In Python, a is a name - it points to an object, in this case, a list.
In your first example, a initially points to the empty list, then to a new list.
In your second example, a points to an empty list, then it is updated to contain the values from the new list. This does not change the list a references.
The difference in the end result is that, as the right hand side of an operation is evaluated first, in both cases, a points to the original list. This means that in the first case, it points to the list that used to be a, while in the second case, it points to itself, making a recursive structure.
If you are having trouble understanding this, I recommend taking a look at it visualized.
The first will point a to a new object, the second will mutate a, so the list referenced by a is still the same.
For example:
a = [1, 2, 3]
b = a
print b # [1, 2, 3]
a[:] = [3, 2, 1]
print b # [3, 2, 1]
a = [1, 2, 3]
#b still references to the old list
print b # [3, 2, 1]
More clear example from #pythonm response
>>> a=[1,2,3,4]
>>> b=a
>>> c=a[:]
>>> a.pop()
4
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> c
[1, 2, 3, 4]
>>>

Why does foo.append(bar) affect all elements in a list of lists?

I create a list of lists and want to append items to the individual lists, but when I try to append to one of the lists (a[0].append(2)), the item gets added to all lists.
a = []
b = [1]
a.append(b)
a.append(b)
a[0].append(2)
a[1].append(3)
print(a)
Gives: [[1, 2, 3], [1, 2, 3]]
Whereas I would expect: [[1, 2], [1, 3]]
Changing the way I construct the initial list of lists, making b a float instead of a list and putting the brackets inside .append(), gives me the desired output:
a = []
b = 1
a.append([b])
a.append([b])
a[0].append(2)
a[1].append(3)
print(a)
Gives: [[1, 2], [1, 3]]
But why? It is not intuitive that the result should be different. I know this has to do with there being multiple references to the same list, but I don't see where that is happening.
It is because the list contains references to objects. Your list doesn't contain [[1 2 3] [1 2 3]], it is [<reference to b> <reference to b>].
When you change the object (by appending something to b), you are changing the object itself, not the list that contains the object.
To get the effect you desire, your list a must contain copies of b rather than references to b. To copy a list you can use the range [:]. For example:
>>> a = []
>>> b = [1]
>>> a.append(b[:])
>>> a.append(b[:])
>>> a[0].append(2)
>>> a[1].append(3)
>>> print a
[[1, 2], [1, 3]]
The key is this part:
a.append(b)
a.append(b)
You are appending the same list twice, so both a[0] and a[1] are references to the same list.
In your second example, you are creating new lists each time you call append like a.append([b]), so they are separate objects that are initialized with the same float value.
In order to make a shallow copy of a list, the idiom is
a.append(b[:])
which when doubled will cause a to have two novel copies of the list b which will not give you the aliasing bug you report.

Assigning a value to an element of a slice in Python

This is a simple question about how Python handles data and variables. I've done a lot of experimenting and have Python mostly figured out, except this keeps tripping me up:
[edit: I separated and rearranged the examples for clarity]
Example 1:
>>> a = [[1], 2]
>>> a[0:1]
[[1]]
>>> a[0:1] = [[5]]
>>> a
[[5], 2] # The assignment worked.
Example 2:
>>> a = [[1], 2]
>>> a[0:1][0]
[1]
>>> a[0:1][0] = [5]
>>> a
[[1], 2] # No change?
Example 3:
>>> a = [[1], 2]
>>> a[0:1][0][0]
1
>>> a[0:1][0][0] = 5
>>> a
[[5], 2] # Why now?
Can anybody explain to me what's going on here?
So far the answers seem to claim that a[0:1] returns a new list containing a reference to the first element of a. But I don't see how that explains Example 1.
a[0:1] is returning a new array which contains a reference to the array [1], thus you end up modifying the inner array via a reference call.
The reason the first case doesn't modify the [1] array is that you're assigning the copied outer array a new inner array value.
Bottom line - a[0:1] returns a copy of the data, but the inner data is not copied.
My understanding is slicing returns a new object. That is it's return value is a new list.
Hence you can not use an assignment operator to changes the values of the original list
>>> a = [[1], 2, 3]
>>> k = a[0:2]
>>> id(a)
4299352904
>>> id(k)
4299353552
>>>
>>> id(a)
4299352904
>>> id(a[0:2])
4299352832
some more plays along the lines
>>> k = 5
>>>
>>> id(k)
4298182344
>>> a[0] = [1,2]
>>> a
[[1, 2], 2, 3]
>>> id(a)
4299352904
>>>
[Edit: on second part of question]
>>> a[0:1] = [[5]]
The following notation is also called commonly as slice assignment
The behavior for builtin lists is atomic (delete + insert) happens in one go. My understanding is that this is not allowed for custom sequence.
There are three distinct operations with indices, all are translated to method calls:
a[i] = b => a.__setitem__(i, b)
del a[i] => a.__delitem__(i)
a[i] used as an expression => a.__getitem__(i)
Here a, b and i are expressions, and i can contain slice objects created using the colon shorthand syntax. E.g.:
>>> class C(object):
... def __setitem__(self, *a):
... print a
...
>>> C()[1] = 0
(1, 0)
>>> C()['foo'] = 0
('foo', 0)
>>> C()['foo':'bar'] = 0
(slice('foo', 'bar', None), 0)
>>> C()['foo':'bar',5] = 0
((slice('foo', 'bar', None), 5), 0)
So what's happening in your third example is this:
a[0:1][0][0] = 5
becomes
a.__getitem__(slice(0,1)).__getitem__(0).__setitem__(0, 5)
The first __getitem__ returns a copy of part of the list, but the second __getitem__ returns the actual list inside that, which is then modified using __setitem__.
Your second example on the other hand becomes
a.__getitem__(slice(0,1)).__setitem__(0, 5)
So __setitem__ is being called on the sliced copy, leaving the original list intact.

Categories