python list by value not by reference
Question:
Let’s take an example
a=['help', 'copyright', 'credits', 'license']
b=a
b.append('XYZ')
b
['help', 'copyright', 'credits', 'license', 'XYZ']
a
['help', 'copyright', 'credits', 'license', 'XYZ']
I wanted to append value in list ‘b’ but the value of list ‘a’ have also changed.
I think I have little idea why its like this (python passes lists by reference).
My question is “how can I pass it by value so that appending ‘b’ does’t change values in ‘a’ ?”
Answers:
You cannot pass anything by value in Python. If you want to make a copy of a
, you can do so explicitly, as described in the official Python FAQ:
b = a[:]
To create a copy of a list do this:
b = a[:]
Also, you can do:
b = list(a)
This will work for any sequence, even those that don’t support indexers and slices…
b = list(a)
When you do b = a
you simply create another pointer to the same memory of a,
that’s why when you append to b , a changes too.
You need to create copy of a and that’s done like this:
b = a[:]
To copy a list you can use list(a)
or a[:]
. In both cases a new object is created.
These two methods, however, have limitations with collections of mutable objects as inner objects keep their references intact:
>>> a = [[1,2],[3],[4]]
>>> b = a[:]
>>> c = list(a)
>>> c[0].append(9)
>>> a
[[1, 2, 9], [3], [4]]
>>> c
[[1, 2, 9], [3], [4]]
>>> b
[[1, 2, 9], [3], [4]]
>>>
If you want a full copy of your objects you need copy.deepcopy
>>> from copy import deepcopy
>>> a = [[1,2],[3],[4]]
>>> b = a[:]
>>> c = deepcopy(a)
>>> c[0].append(9)
>>> a
[[1, 2], [3], [4]]
>>> b
[[1, 2], [3], [4]]
>>> c
[[1, 2, 9], [3], [4]]
>>>
I found that we can use extend() to implement the function of copy()
a=['help', 'copyright', 'credits', 'license']
b = []
b.extend(a)
b.append("XYZ")
In terms of performance my favorite answer would be:
b.extend(a)
Check how the related alternatives compare with each other in terms of performance:
In [1]: import timeit
In [2]: timeit.timeit('b.extend(a)', setup='b=[];a=range(0,10)', number=100000000)
Out[2]: 9.623248100280762
In [3]: timeit.timeit('b = a[:]', setup='b=[];a=range(0,10)', number=100000000)
Out[3]: 10.84756088256836
In [4]: timeit.timeit('b = list(a)', setup='b=[];a=range(0,10)', number=100000000)
Out[4]: 21.46313500404358
In [5]: timeit.timeit('b = [elem for elem in a]', setup='b=[];a=range(0,10)', number=100000000)
Out[5]: 66.99795293807983
In [6]: timeit.timeit('for elem in a: b.append(elem)', setup='b=[];a=range(0,10)', number=100000000)
Out[6]: 67.9775960445404
In [7]: timeit.timeit('b = deepcopy(a)', setup='from copy import deepcopy; b=[];a=range(0,10)', number=100000000)
Out[7]: 1216.1108016967773
If you want to copy a one-dimensional list, use
b = a[:]
However, if a
is a 2-dimensional list, this is not going to work for you. That is, any changes in a
will also be reflected in b
. In that case, use
b = [[a[x][y] for y in range(len(a[0]))] for x in range(len(a))]
As mentioned by phihag in his answer,
b = a[:]
will work for your case since slicing a list creates a new memory id of the list (meaning you are no longer referencing the same object in your memory and the changes you make to one will not be reflected in the other.)
However, there is a slight problem. If your list is multidimensional, as in lists within lists, simply slicing will not solve this problem. Changes made in the higher dimensions, i.e. the lists within the original list, will be shared between the two.
Do not fret, there is a solution. The module copy has a nifty copying technique that takes care of this issue.
from copy import deepcopy
b = deepcopy(a)
will copy a list with a new memory id no matter how many levels of lists it contains!
I would recommend the following solution:
b = []
b[:] = a
This will copy all the elements from a to b. The copy will be value copy, not reference copy.
Let’s take an example
a=['help', 'copyright', 'credits', 'license']
b=a
b.append('XYZ')
b
['help', 'copyright', 'credits', 'license', 'XYZ']
a
['help', 'copyright', 'credits', 'license', 'XYZ']
I wanted to append value in list ‘b’ but the value of list ‘a’ have also changed.
I think I have little idea why its like this (python passes lists by reference).
My question is “how can I pass it by value so that appending ‘b’ does’t change values in ‘a’ ?”
You cannot pass anything by value in Python. If you want to make a copy of a
, you can do so explicitly, as described in the official Python FAQ:
b = a[:]
To create a copy of a list do this:
b = a[:]
Also, you can do:
b = list(a)
This will work for any sequence, even those that don’t support indexers and slices…
b = list(a)
When you do b = a
you simply create another pointer to the same memory of a,
that’s why when you append to b , a changes too.
You need to create copy of a and that’s done like this:
b = a[:]
To copy a list you can use list(a)
or a[:]
. In both cases a new object is created.
These two methods, however, have limitations with collections of mutable objects as inner objects keep their references intact:
>>> a = [[1,2],[3],[4]]
>>> b = a[:]
>>> c = list(a)
>>> c[0].append(9)
>>> a
[[1, 2, 9], [3], [4]]
>>> c
[[1, 2, 9], [3], [4]]
>>> b
[[1, 2, 9], [3], [4]]
>>>
If you want a full copy of your objects you need copy.deepcopy
>>> from copy import deepcopy
>>> a = [[1,2],[3],[4]]
>>> b = a[:]
>>> c = deepcopy(a)
>>> c[0].append(9)
>>> a
[[1, 2], [3], [4]]
>>> b
[[1, 2], [3], [4]]
>>> c
[[1, 2, 9], [3], [4]]
>>>
I found that we can use extend() to implement the function of copy()
a=['help', 'copyright', 'credits', 'license']
b = []
b.extend(a)
b.append("XYZ")
In terms of performance my favorite answer would be:
b.extend(a)
Check how the related alternatives compare with each other in terms of performance:
In [1]: import timeit
In [2]: timeit.timeit('b.extend(a)', setup='b=[];a=range(0,10)', number=100000000)
Out[2]: 9.623248100280762
In [3]: timeit.timeit('b = a[:]', setup='b=[];a=range(0,10)', number=100000000)
Out[3]: 10.84756088256836
In [4]: timeit.timeit('b = list(a)', setup='b=[];a=range(0,10)', number=100000000)
Out[4]: 21.46313500404358
In [5]: timeit.timeit('b = [elem for elem in a]', setup='b=[];a=range(0,10)', number=100000000)
Out[5]: 66.99795293807983
In [6]: timeit.timeit('for elem in a: b.append(elem)', setup='b=[];a=range(0,10)', number=100000000)
Out[6]: 67.9775960445404
In [7]: timeit.timeit('b = deepcopy(a)', setup='from copy import deepcopy; b=[];a=range(0,10)', number=100000000)
Out[7]: 1216.1108016967773
If you want to copy a one-dimensional list, use
b = a[:]
However, if a
is a 2-dimensional list, this is not going to work for you. That is, any changes in a
will also be reflected in b
. In that case, use
b = [[a[x][y] for y in range(len(a[0]))] for x in range(len(a))]
As mentioned by phihag in his answer,
b = a[:]
will work for your case since slicing a list creates a new memory id of the list (meaning you are no longer referencing the same object in your memory and the changes you make to one will not be reflected in the other.)
However, there is a slight problem. If your list is multidimensional, as in lists within lists, simply slicing will not solve this problem. Changes made in the higher dimensions, i.e. the lists within the original list, will be shared between the two.
Do not fret, there is a solution. The module copy has a nifty copying technique that takes care of this issue.
from copy import deepcopy
b = deepcopy(a)
will copy a list with a new memory id no matter how many levels of lists it contains!
I would recommend the following solution:
b = []
b[:] = a
This will copy all the elements from a to b. The copy will be value copy, not reference copy.