Iteratively add elements to a list - python

I am trying to add elements of a list in Python and thereby generate a list of lists. Suppose I have two lists a = [1,2] and b = [3,4,5]. How can I construct the following list:
c = [[1,2,3],[1,2,4],[1,2,5]] ?
In my futile attempts to generate c, I stumbled on an erroneous preconception of Python which I would like to describe in the following. I would be grateful for someone elaborating a bit on the conceptual question posed at the end of the paragraph. I tried (among other things) to generate c as follows:
c = []
for i in b:
temp = a
temp.extend([i])
c += [temp]
What puzzled me was that a seems to be overwritten by temp. Why does this happen? It seems that the = operator is used in the mathematical sense by Python but not as an assignment (in the sense of := in mathematics).

You are not creating a copy; temp = a merely makes temp reference the same list object. As a result, temp.extend([i]) extends the same list object a references:
>>> a = []
>>> temp = a
>>> temp.extend(['foo', 'bar'])
>>> a
['foo', 'bar']
>>> temp is a
True
You can build c with a list comprehension:
c = [a + [i] for i in b]
By concatenating instead of extending, you create a new list object each iteration.
You could instead also have made an actual copy of a with:
temp = a[:]
where the identity slice (slicing from beginning to end) creates a new list containing a shallow copy.

Related

Generate list of copies in Python

I want to generate list of 100 copies of the same sentence with one char modified in each copy. I am currently learning python and I don't understand why something like this:
def generate_copy(seq):
copies = [seq for i in range(100)]
for copy in copies:
copy[random.randint(0, len(seq) - 1)] = random.choice(char_list)
print(''.join(copy))
return copies
modifies all copies. I want to get something like this: ['AAAB', 'BAAA', 'ZAAA', ...], like they are independent from each other, but I get: ['AAAB', 'ZAAB', 'ZCAB', ...]. How can I do it?
Not sure how did you get result you pasted - string in python is immutable. copies is list of strings. So copy is string. You cannot type copy[0] = 'a' as you will get TypeError: 'str' object does not support item assignment cause of that.
What do you want to do is build changed sequence when you create copies.
def change_random_letter(seq, char_list):
i = random.randint(0, len(seq) - 1)
new_letter = random.choice(char_list)
return f"{seq[:i]}{new_letter}{seq[i+1:]}"
def generate_copy(seq):
copies = [change_random_letter(seq, char_list) for _ in range(100)]
return copies
EDIT: I noticed you pass list as seq. If so, copies are not a copies! Look
l = [1, 2]
ll = [l, l]
ll[0][0] = "A"
print(ll) # [['A', 2], ['A', 2]]
When type is mutable, by using a variable you use the reference. So all copies elements points to the same place in memory. You need to use copy function of list.
copies = [seq.copy() for _ in range(100)]

Why can't a list be constructed and modified in the same line?

For example, why is a not equal to b?
a = [1]
a.append(2)
print(a) # [1, 2]
b = [1].append(2)
print(b) # None
The syntax for b doesn't look wrong to me, but it is. I want to write one-liners to define a list (e.g. using a generator expression) and then append elements, but all I get is None.
It's because:
append, extend, sort and more list function are all "in-place".
What does "in-place" mean? it means it modifies the original variable directly, some things you would need:
l = sorted(l)
To modify the list, but append already does that, so:
l.append(3)
Will modify l already, don't need:
l = l.append(3)
If you do:
l = [1].append(2)
Yes it will modify the list of [1], but it would be lost in memory somewhere inaccessible, whereas l will become None as we discovered above.
To make it not "in-place", without using append either do:
l = l + [2]
Or:
l = [*l, 2]
The one-liner for b does these steps:
Defines a list [1]
Appends 2 to the list in-place
Append has no return, so b = None
The same is true for all list methods that alter the list in-place without a return. These are all None:
c = [1].extend([2])
d = [2, 1].sort()
e = [1].insert(1, 2)
...
If you wanted a one-liner that is similar to your define and extend, you could do
c2 = [1, *[2]]
which you could use to combine two generator expressions.
All built-in methods under class 'List' in Python are just modifying the list 'in situ'. They only change the original list and return nothing.
The advantage is, you don't need to pass the object to the original variable every time you modify it. Meanwhile, you can't accumulatively call its methods in one line of code such as what is used in Javascript. Because Javascript always turns its objects into DOM, but Python not.

Strange behaviour when changing value of list in a list

While coding on a python project I noticed a strange behavior when I tried to change the value of a list in another list.
Not working code:
lst = []
to_add = [None,None,None]
for i in range(3):
lst.append(to_add)
for i in range(3):
lst[i][0] = anotherslistoflists[i][0]
To my surprise this example gave unexpected results. To be specific, every lst[i][0] got assigned with the same value which was the first element from the last index of the "anotherlistoflists" list.
Result example (when printing lst)
("word1",None,None)
("word1",None,None)
("word1",None,None)
Working code:
lst = []
for i in range(5):
lst.append([None,None,None])
# same code as above
Result example2 (expected result)
("word1",None,None)
("word2",None,None)
("word3",None,None)
Of course, as you can see the problem is solved but I was wondering why the first code does not work. Something is probably wrong with the "to_add" list but I don't understand what is the problem here.
You are appending the same list, to_add, three times. Then, once you modify it, all three items which point to it will reflect the same change. If you want to create a new copy with the same values, you could use the built-in list function:
for i in range(3):
lst.append(list(to_add))
You are adding the same list, to_add to lst:
>>> a = [None]
>>> b = [a]
>>> c = [a]
>>> print a, b, c
[None] [[None]] [[None]]
>>> a[0] = 1
>>> print a, b, c
[1] [[1]] [[1]]
In the first case you are not adding a copy of add_to_list you are actually adding that exact list over and over, so when you change it later you're changing the real add_to_list and everywhere that refers to it. In the second case you're adding a new list containing None each time, and modifying different lists each time through

Python - external variable being changed by function

I am trying to write a function that squares each value in a list, returning a new list with individual values squared. I pass a list to it and it changes the original list. I'd like it to not make any changes to the original list, so that I can use it in other functions.
def squareset(c):
d=c
count = len(c)
print(c)
for i in range(0,(count),1):
d[i]=d[i]**2
return d
test = [1,2,3]
print(squareset(test))
print(test)
I don't have this problem with functions operating on simple variables of type int or float.
I added the d=c line trying to prevent the change to the list test, but it makes no difference. print(test) is producing the result [1,4,9] instead of [1,2,3]. Why is this happening and how can I fix this?
Doing d=c simply makes the parameter d point to the same object that c is pointing to. Hence, every change made to d is made to the same object that c points to.
If you want to avoid changing c you'll have to either send a copy of the object, or make a copy of the object inside the function and use this copy.
For example, do:
d = [i for i in c]
or:
d = c[:]
instead of:
d = c
Assigning the list to another variable doesn't copy the list. To copy, just
def squareset(c):
d=c[:]
...
While the other answers provided are correct I would suggest using list comprehension to square your list.
In [4]:
test = [1,2,3]
results = [elm**2 for elm in test]
print test
print results
[1, 2, 3]
[1, 4, 9]
If you wanted a function:
def squareList(lst):
return [elm**2 for elm in lst]
Try this:
def square(var):
return [x*x for x in var]
x = [1,2,3]
z = square(x)

Why are lists linked in Python in a persistent way?

A variable is set. Another variable is set to the first. The first changes value. The second does not. This has been the nature of programming since the dawn of time.
>>> a = 1
>>> b = a
>>> b = b - 1
>>> b
0
>>> a
1
I then extend this to Python lists. A list is declared and appended. Another list is declared to be equal to the first. The values in the second list change. Mysteriously, the values in the first list, though not acted upon directly, also change.
>>> alist = list()
>>> blist = list()
>>> alist.append(1)
>>> alist.append(2)
>>> alist
[1, 2]
>>> blist
[]
>>> blist = alist
>>> alist.remove(1)
>>> alist
[2]
>>> blist
[2]
>>>
Why is this?
And how do I prevent this from happening -- I want alist to be unfazed by changes to blist (immutable, if you will)?
Python variables are actually not variables but references to objects (similar to pointers in C). There is a very good explanation of that for beginners in http://foobarnbaz.com/2012/07/08/understanding-python-variables/
One way to convince yourself about this is to try this:
a=[1,2,3]
b=a
id(a)
68617320
id(b)
68617320
id returns the memory address of the given object. Since both are the same for both lists it means that changing one affects the other, because they are, in fact, the same thing.
Variable binding in Python works this way: you assign an object to a variable.
a = 4
b = a
Both point to 4.
b = 9
Now b points to somewhere else.
Exactly the same happens with lists:
a = []
b = a
b = [9]
Now, b has a new value, while a has the old one.
Till now, everything is clear and you have the same behaviour with mutable and immutable objects.
Now comes your misunderstanding: it is about modifying objects.
lists are mutable, so if you mutate a list, the modifications are visible via all variables ("name bindings") which exist:
a = []
b = a # the same list
c = [] # another empty one
a.append(3)
print a, b, c # a as well as b = [3], c = [] as it is a different one
d = a[:] # copy it completely
b.append(9)
# now a = b = [3, 9], c = [], d = [3], a copy of the old a resp. b
What is happening is that you create another reference to the same list when you do:
blist = alist
Thus, blist referes to the same list that alist does. Thus, any modifications to that single list will affect both alist and blist.
If you want to copy the entire list, and not just create a reference, you can do this:
blist = alist[:]
In fact, you can check the references yourself using id():
>>> alist = [1,2]
>>> blist = []
>>> id(alist)
411260888
>>> id(blist)
413871960
>>> blist = alist
>>> id(blist)
411260888
>>> blist = alist[:]
>>> id(blist)
407838672
This is a relevant quote from the Python docs.:
Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.
Based on this post:
Python passes references-to-objects by value (like Java), and
everything in Python is an object. This sounds simple, but then you
will notice that some data types seem to exhibit pass-by-value
characteristics, while others seem to act like pass-by-reference...
what's the deal?
It is important to understand mutable and immutable objects. Some
objects, like strings, tuples, and numbers, are immutable. Altering
them inside a function/method will create a new instance and the
original instance outside the function/method is not changed. Other
objects, like lists and dictionaries are mutable, which means you can
change the object in-place. Therefore, altering an object inside a
function/method will also change the original object outside.
So in your example you are making the variable bList and aList point to the same object. Therefore when you remove an element from either bList or aList it is reflected in the object that they both point to.
The short answer two your question "Why is this?": Because in Python integers are immutable, while lists are mutable.
You were looking for an official reference in the Python docs. Have a look at this section:
http://docs.python.org/2/reference/simple_stmts.html#assignment-statements
Quote from the latter:
Assignment statements are used to (re)bind names to values and to
modify attributes or items of mutable objects
I really like this sentence, have never seen it before. It answers your question precisely.
A good recent write-up about this topic is http://nedbatchelder.com/text/names.html, which has already been mentioned in one of the comments.

Categories