Python List mutable - python

I am trying to use Python term to explain why the following happens, can somebody explain why tmp becomes to [[1,2,3]] not stay as [[1,2]]?
arr = []
tmp = [1,2]
arr.append(tmp)
print arr # [[1,2]]
tmp.append(3)
print arr # [[1,2,3]]

arr = [] is an empty list, and when you append tmp to it via:
tmp = [1, 2]
arr.append(tmp)
You are putting tmp in the arr list, thus giving you arr = [tmp] which can be expanded to arr = [[1,2]]. But the neat thing here is that you maintain a reference to to the list, [1,2] via the temp variable. Thus, when you append temp you are appending the same list that is in arr.
For a bit further clarification, just because you are appending tmp to arr doesn't mean that the resulting list [[1,2]] is all going to be one continuous block in memory. You are going to have the arr list and the first element of arr is going to be a pointer to the list tmp.

All the comments are great ones.
arr.append(tmp)
print arr # [[1,2]]
As you can see, the result is NOT:
print arr # [1,2]
So, arr just holds the reference to tmp array. If my guess is write you are looking for:
arr.extend(tmp)
print arr # [1,2]
More on difference between append vs. extend list methods in python

That's because of both tmp and arr[0] points to the same object.
Just check it here, step by step:
http://www.pythontutor.com/visualize.html
First print statement
Second print statement
You can manually check it by using id built-in
>>> arr = []
>>> tmp = [1,2]
>>> arr.append(tmp)
>>> id(tmp)
4404123192
>>> id(arr[0])
4404123192
>>> assert id(tmp) == id(arr[0])
>>> tmp.append(3) # allocate more memory (if needs) and add '3' to object (list) with id 4404123192
>>> id(tmp)
4404123192
>>> id(arr[0])
4404123192
>>> print arr
[[1, 2, 3]]

Related

Why python call by reference is so unruly

arr = [1]
def f1(lst):
lst.append(2)
print(lst)
lst = 2
print(lst)
f1(arr)
print(arr) # [1,2]
why python call by reference parameter does not change to value?
what does lst variable indeicating when do "lst = 2"
(not connected to arr?)
Assigning lst = 2 doesn't affect the value of arr. In fact, python doesn't do "call by reference" at all.
Annotating your code with comments that might help clear it up:
arr = [1]
def f1(lst):
# lst and arr both refer to the same [1] list at this point.
# Two different and independent names for the same object.
lst.append(2) # appends 2 to [1], aka lst, aka arr
print(lst) # lst/arr is now [1, 2]
lst = 2 # reassign the name 'lst' to the value 2!
# At this point, lst refers to 2 instead of [1, 2].
# lst and arr are no longer connected.
# arr is still [1, 2] even though lst is 2.
print(lst) # indeed, lst is now 2
# but arr is still [1, 2], as seen below:
f1(arr)
print(arr) # [1,2]
https://nedbatchelder.com/text/names.html is highly recommended reading on this topic! The main thing to understand is that lst and arr are just different names that at different points in the code might refer to the same value or different values.
When you call lst.append, you are modifying the value that lst is a name for, which arr also happens to be a name for. When you say lst = 2, you are rebinding the name lst, but you are not modifying the value that it previously referred to (and to which arr still refers).

Indexing a 2D List with another List

If I have a list such as:
lst = [[1,2,3], [4,5,6], [7,8,9]]
and another list which contains a [row, column] index for an element in the list, so for example:
index = [2, 1]
Obviously I can get the element at index with:
lst[index[0]][index[1]]
However, I find that this syntax is quite clunky and doesn't sit well in a big block of code. It would also become worse with a higher dimensional list.
My question: is there an easier way to do this index?
It would seem that something like:
lst[index]
would be more readable but this isn't how Python works so isn't an option.
Since you're working with a 2D-list, it might be a good idea to use numpy. You'll then simply need to define index as a tuple. Index 3 would be out of range, though:
>>> import numpy as np
>>> a = np.array([[1,2,3], [4,5,6], [7,8,9]])
>>> index = (1, 2)
>>> a[index]
6
The method you're looking for is called Array#dig in Ruby:
[[1,2,3], [4,5,6], [7,8,9]].dig(1, 2)
# 6
but I couldn't find any plain Python equivalent.
You could just create a simple function that iterates over the index. For every element in index just fetch item from object and assign that as a new object. Once you have iterated over the whole index return current object. As #EricDuminil noted it works with dicts and all other objects that support __getitem__:
def index(obj, idx):
for i in idx:
obj = obj[i]
return obj
LST = [[1,2,3], [4,[5],6], [{'foo': {'bar': 'foobar'}},8,9]]
INDEXES = [[2, 2], [1, 1, 0], [2, 0, 'foo', 'bar']]
for i in INDEXES:
print('{0} -> {1}'.format(i, index(LST, i)))
Output:
[2, 2] -> 9
[1, 1, 0] -> 5
[2, 0, 'foo', 'bar'] -> foobar
The method you used is probably the simplest way, but for better readability you could go for something like
i = index[0]
j = index[1]
x = lst[i][j]
One way or another, you need to split your index list into the 2 values. If you want to declutter your main block of code, you can write a function to handle this, but that would hardly be "easier".
EDIT: As suggested below, tuple unpacking is an even better option
i, j = index
x = lst[i][j]

Short "if" in python in case of append into array

I would like to use an inline if statement to append data to an array if it is not already in the array, as in:
arr.append( data if not data in arr )
But this code returns:
SyntaxError: invalid syntax
Is there any other option?
Use:
arr.extend([data] if data not in arr else [])
Examples
Let's start with a sample array:
>>> arr = [1, 2, 3]
Now let's try data = 4 which is not in arr:
>>> data = 4
>>> arr.extend([data] if data not in arr else [])
>>> arr
[1, 2, 3, 4]
Now let's try with data = 2 which is a value already in arr:
>>> data = 2
>>> arr.extend([data] if data not in arr else [])
>>> arr
[1, 2, 3, 4]
This leaves arr unchanged as it should.
Notes
append will append something. Since it seems that we only want to append if data is not in arr, this is not the right method to use. The method extend avoids this problem.
[data] if data not in arr else [] will return [data] if data is not in arr. Otherwise, it will return [].
arr.extend([]) leaves arr unchanged.
arr.extend([data]) adds the element data to the end of arr.
Inline if statements must contain an else statement too, as in data if not data in arr else 3
Note that this example does not work.
arr if data in arr else arr.append(data)
(This returns arr unchanged if data already exists in it else appends. (Yes, we are abusing side effects))
>>> L = [1,2,3]
>>> a = 4
>>> L if a in L else L.append(a)
>>> L
[1, 2, 3, 4]
>>> L if a in L else L.append(a)
[1, 2, 3, 4]

Appending lists to a global list in python

I'm trying to generate a list of permutations (also lists) by appending a global list as each permutation is generated. I can tell the permutations are being generated properly by printing each after it is generated. However, after the function is called my global list only contains the original list for each time a permutation was generated by the function.
array = []
def perms(a, k):
if (k == len(a)):
global array
array.append(a)
print(a) #perms function is working properly
else:
for i in range(k, len(a)):
a[k], a[i] = a[i], a[k]
perms(a, k+1)
a[k], a[i] = a[i], a[k]
perms([1,2,3,4], 0)
#only prints the original list for each time a perm was generated
for i in array: print(i)
When I am printing at the bottom it is showing array = [[1,2,3,4], [1,2,3,4], ...]
It seems like my global list array can only see the perms parameter a in the scope it was called in.
How would this be done properly?
In Python, lists are mutable. So when you call perms(a, k+1) inside perms, you're ending up with the exact same instance of a inside the recursive call. Consider this example:
>>> a = []
>>> b = [4,5,6]
>>> a.append(b)
>>> print a
[[4, 5, 6]]
>>> b.append(7)
>>> print a
[[4, 5, 6, 7]]
As you can see, editing the b list directly also edits the b that was appended to the a list, because both b and a[0] are referencing the exact same object:
>>> b is a[0]
True
You don't see this behavior with strings, which are immutable:
>>> a = []
>>> b = "hi"
>>> a.append(b)
>>> print a
['hi']
>>> b = "blah"
>>> print a
['hi']
>>> b is a[0]
False
You can fix the issue in your code by passing a copy of the a list into the recursive call, like this:
perms(a[:], k+1)

Create an empty list with certain size in Python

How do I create an empty list that can hold 10 elements?
After that, I want to assign values in that list. For example:
xs = list()
for i in range(0, 9):
xs[i] = i
However, that gives IndexError: list assignment index out of range. Why?
Editor's note:
In Python, lists do not have a set capacity, but it is not possible to assign to elements that aren't already present. Answers here show code that creates a list with 10 "dummy" elements to replace later. However, most beginners encountering this problem really just want to build a list by adding elements to it. That should be done using the .append method, although there will often be problem-specific ways to create the list more directly. Please see Why does this iterative list-growing code give IndexError: list assignment index out of range? How can I repeatedly add elements to a list? for details.
You cannot assign to a list like xs[i] = value, unless the list already is initialized with at least i+1 elements. Instead, use xs.append(value) to add elements to the end of the list. (Though you could use the assignment notation if you were using a dictionary instead of a list.)
Creating an empty list:
>>> xs = [None] * 10
>>> xs
[None, None, None, None, None, None, None, None, None, None]
Assigning a value to an existing element of the above list:
>>> xs[1] = 5
>>> xs
[None, 5, None, None, None, None, None, None, None, None]
Keep in mind that something like xs[15] = 5 would still fail, as our list has only 10 elements.
range(x) creates a list from [0, 1, 2, ... x-1]
# 2.X only. Use list(range(10)) in 3.X.
>>> xs = range(10)
>>> xs
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Using a function to create a list:
>>> def display():
... xs = []
... for i in range(9): # This is just to tell you how to create a list.
... xs.append(i)
... return xs
...
>>> print display()
[0, 1, 2, 3, 4, 5, 6, 7, 8]
List comprehension (Using the squares because for range you don't need to do all this, you can just return range(0,9) ):
>>> def display():
... return [x**2 for x in range(9)]
...
>>> print display()
[0, 1, 4, 9, 16, 25, 36, 49, 64]
Try this instead:
lst = [None] * 10
The above will create a list of size 10, where each position is initialized to None. After that, you can add elements to it:
lst = [None] * 10
for i in range(10):
lst[i] = i
Admittedly, that's not the Pythonic way to do things. Better do this:
lst = []
for i in range(10):
lst.append(i)
Or even simpler, in Python 2.x you can do this to initialize a list with values from 0 to 9:
lst = range(10)
And in Python 3.x:
lst = list(range(10))
varunl's currently accepted answer
>>> l = [None] * 10
>>> l
[None, None, None, None, None, None, None, None, None, None]
Works well for non-reference types like numbers. Unfortunately if you want to create a list-of-lists you will run into referencing errors. Example in Python 2.7.6:
>>> a = [[]]*10
>>> a
[[], [], [], [], [], [], [], [], [], []]
>>> a[0].append(0)
>>> a
[[0], [0], [0], [0], [0], [0], [0], [0], [0], [0]]
>>>
As you can see, each element is pointing to the same list object. To get around this, you can create a method that will initialize each position to a different object reference.
def init_list_of_objects(size):
list_of_objects = list()
for i in range(0,size):
list_of_objects.append( list() ) #different object reference each time
return list_of_objects
>>> a = init_list_of_objects(10)
>>> a
[[], [], [], [], [], [], [], [], [], []]
>>> a[0].append(0)
>>> a
[[0], [], [], [], [], [], [], [], [], []]
>>>
There is likely a default, built-in python way of doing this (instead of writing a function), but I'm not sure what it is. Would be happy to be corrected!
Edit: It's [ [] for _ in range(10)]
Example :
>>> [ [random.random() for _ in range(2) ] for _ in range(5)]
>>> [[0.7528051908943816, 0.4325669600055032], [0.510983236521753, 0.7789949902294716], [0.09475179523690558, 0.30216475640534635], [0.3996890132468158, 0.6374322093017013], [0.3374204010027543, 0.4514925173253973]]
There are two "quick" methods:
x = length_of_your_list
a = [None]*x
# or
a = [None for _ in xrange(x)]
It appears that [None]*x is faster:
>>> from timeit import timeit
>>> timeit("[None]*100",number=10000)
0.023542165756225586
>>> timeit("[None for _ in xrange(100)]",number=10000)
0.07616496086120605
But if you are ok with a range (e.g. [0,1,2,3,...,x-1]), then range(x) might be fastest:
>>> timeit("range(100)",number=10000)
0.012513160705566406
You can .append(element) to the list, e.g.:
s1.append(i)
What you are currently trying to do is access an element (s1[i]) that does not exist.
I'm surprised nobody suggest this simple approach to creating a list of empty lists. This is an old thread, but just adding this for completeness. This will create a list of 10 empty lists
x = [[] for i in range(10)]
The accepted answer has some gotchas. For example:
>>> a = [{}] * 3
>>> a
[{}, {}, {}]
>>> a[0]['hello'] = 5
>>> a
[{'hello': 5}, {'hello': 5}, {'hello': 5}]
>>>
So each dictionary refers to the same object. Same holds true if you initialize with arrays or objects.
You could do this instead:
>>> b = [{} for i in range(0, 3)]
>>> b
[{}, {}, {}]
>>> b[0]['hello'] = 6
>>> b
[{'hello': 6}, {}, {}]
>>>
How do I create an empty list that can hold 10 elements?
All lists can hold as many elements as you like, subject only to the limit of available memory. The only "size" of a list that matters is the number of elements currently in it.
However, that gives IndexError: list assignment index out of range. Why?
The first time through the loop, i is equal to 0. Thus, we attempt xs[0] = 0. This does not work because there are currently 0 elements in the list, so 0 is not a valid index.
We cannot use indexing to write list elements that don't already exist - we can only overwrite existing ones. Instead, we should use the .append method:
xs = list();
for i in range(0, 9):
xs.append(i)
The next problem you will note is that your list will actually have only 9 elements, because the end point is skipped by the range function. (As side notes: [] works just as well as list(), the semicolon is unnecessary, and only one parameter is needed for range if you're starting from 0.) Addressing those issues gives:
xs = []
for i in range(10):
xs.append(i)
However, this is still missing the mark - range is not some magical keyword that's part of the language the way for (or, say, def) is.
In 2.x, range is a function, which directly returns the list that we already wanted:
xs = range(10) # 2.x specific!
# In 3.x, we don't get a list; we can do a lot of things with the
# result, but we can't e.g. append or replace elements.
In 3.x, range is a cleverly designed class, and range(10) creates an instance. To get the desired list, we can simply feed it to the list constructor:
xs = list(range(10)) # correct in 3.x, redundant in 2.x
One simple way to create a 2D matrix of size n using nested list comprehensions:
m = [[None for _ in range(n)] for _ in range(n)]
I'm a bit surprised that the easiest way to create an initialised list is not in any of these answers. Just use a generator in the list function:
list(range(9))
Another option is to use numpy for fixed size arrays (of pointers):
> pip install numpy
import numpy as np
a = np.empty(10, dtype=np.object)
a[1] = 2
a[5] = "john"
a[3] = []
If you just want numbers, you can do with numpy:
a = np.arange(10)
Here's my code for 2D list in python which would read no. of rows from the input :
empty = []
row = int(input())
for i in range(row):
temp = list(map(int, input().split()))
empty.append(temp)
for i in empty:
for j in i:
print(j, end=' ')
print('')
A list is always "iterable" and you can always add new elements to it:
insert: list.insert(indexPosition, value)
append: list.append(value)
extend: list.extend(value)
In your case, you had instantiated an empty list of length 0. Therefore, when you try to add any value to the list using the list index (i), it is referring to a location that does not exist. Therefore, you were getting the error "IndexError: list assignment index out of range".
You can try this instead:
s1 = list();
for i in range(0,9):
s1.append(i)
print (s1)
To create a list of size 10(let's say), you can first create an empty array, like np.empty(10) and then convert it to list using arrayName.tolist(). Alternately, you can chain them as well.
**`np.empty(10).tolist()`**
I came across this SO question while searching for a similar problem. I had to build a 2D array and then replace some elements of each list (in 2D array) with elements from a dict.
I then came across this SO question which helped me, maybe this will help other beginners to get around.
The key trick was to initialize the 2D array as an numpy array and then using array[i,j] instead of array[i][j].
For reference this is the piece of code where I had to use this :
nd_array = []
for i in range(30):
nd_array.append(np.zeros(shape = (32,1)))
new_array = []
for i in range(len(lines)):
new_array.append(nd_array)
new_array = np.asarray(new_array)
for i in range(len(lines)):
splits = lines[i].split(' ')
for j in range(len(splits)):
#print(new_array[i][j])
new_array[i,j] = final_embeddings[dictionary[str(splits[j])]-1].reshape(32,1)
Now I know we can use list comprehension but for simplicity sake I am using a nested for loop. Hope this helps others who come across this post.
Not technically a list but similar to a list in terms of functionality and it's a fixed length
from collections import deque
my_deque_size_10 = deque(maxlen=10)
If it's full, ie got 10 items then adding another item results in item #index 0 being discarded. FIFO..but you can also append in either direction.
Used in say
a rolling average of stats
piping a list through it aka sliding a window over a list until you get a match against another deque object.
If you need a list then when full just use list(deque object)
s1 = []
for i in range(11):
s1.append(i)
print s1
To create a list, just use these brackets: "[]"
To add something to a list, use list.append()
Make it more reusable as a function.
def createEmptyList(length,fill=None):
'''
return a (empty) list of a given length
Example:
print createEmptyList(3,-1)
>> [-1, -1, -1]
print createEmptyList(4)
>> [None, None, None, None]
'''
return [fill] * length
This code generates an array that contains 10 random numbers.
import random
numrand=[]
for i in range(0,10):
a = random.randint(1,50)
numrand.append(a)
print(a,i)
print(numrand)

Categories

Resources