python OOP unexpected results - python

I have a list containing lists of objects. More specifically,
l = [ [X1,X2],[X3,X4]]
where X1,X2,X3,X4 are objects.
Now, I have a method in the class definition, that takes a list of objects, and modifies the attribute of the present object. Thus,
Class X:
def __init__(self,value=1):
self.value = value
def func (self,l):
total = 0
for x in l:
total += x.value
self.value = total
The problem I encounter is as follows. I have to apply the function func on elements of l[1] using elements of l[0]. However, when I do so, it turns out that the elements of l[0] are also getting changed. Thus, when I input
for obj in l[1]:
obj.func(l[0])
then I see that elements of l[0] have values that should ideally be assigned to l[1].
the list lis created as follows.
l0 = []
for i in range(2):
newx= X(i)
l.append(newx)
l=[]
l.append(l0)
l.append(l0)
What am I missing?
Correction: It seems python doesn't create new list objects every time I append an existing list.I basically need to copy the list using copy command. Hence, it is modifying the existing object.

l.append(l0)
l.append(l0)
Here you are appending the same list twice. Changes to objects in one list will be seen in the other because they are the same list.
To get the result you want, you must create two lists rather than reuse the first list.

Related

Local and Global lists in Python

Please help me as I am new to python
when I call this function the original value of list1 changes
def mystery(list1):
list1[0] , list1[1] = list1[1], list1[0]
list1 = [7,82,44,23,11]
mystery(list1)
print(list1) #prints [82, 7, 44, 23, 11]
how it can change the value of global list1
if I change my function to
def mystery(list1):
list1 = list1 + list1[2:5]
then I am getting the global value of list1 and not the updated one.
lists are mutable objects in python, so if you are passing to a function a list all the changes that you make to the list inside your function will be reflected everywhere/globally
If you pass a mutable object into a method, the method gets a reference to that same object and you can mutate it to your heart's delight, but if you rebind the reference in the method, the outer scope will know nothing about it, and after you're done, the outer reference will still point at the original object.
If you pass an immutable object to a method, you still can't rebind the outer reference, and you can't even mutate the object.[more details here]
The reason list1 is changing is because you're actually modifying the list object inside of the function.
It really has nothing todo with global / local variables, if you renamed the parameter inside your function to something else, and still passed in list1, list1 would be modified.
If you're wanting to return a new list, you need to first create a copy of the list, there's many ways to do this. list(list1) is one way. Then return the list at the end of the function.
If I understand your queston, you want to actually append some more values to the passed in list, use list1.append(...) to add to the end of the list.
And since you're modifying the list itself, it'll change in the larger scope.
But it's still not using the global scope, as you're just using a var with the same name in the local scope.
Maybe just return the list?
def mystery(list1):
return list1 + list1[2:5]
list1 = [7,82,44,23,11]
list1 = mystery(list1)
print(list1)
In python, assigning a value to a variable like a = 5 means you are creating an object in the memory for the value 5. Now a is a link to that object holding 5 (in Layman's terms). If you change a now, say a = "something else", this creates a new object for the string "something else" in somewhere else in the memory and now a is pointing to that. This is why you get to change the data type of python variables.
When you pass something to a python function, that link is the one that is passed on. So when you call mystery(list1), original link to the list1 is passed. Therefore changing an element in the list1 means you are assigning a new value to that particular element in the original list1. No matter you are inside or outside of the function mystery() in this case since you will be using the original link you created to access the element inside list1 to change it. The change happens inside the list1; list1 didn't get reassigned.
However when you do list1 = "something new" inside your function mystery(), you are creating a new variable list1 inside the function which is local to the function. You are not changing the original list1.
Passing mutable objects into a function and then modifying the object inside the function will have the same effect as modifying the object directly.
list1 = [1,2,3]
def func(list1):
list1[0] = 5
>>>list1
[5,2,3]
This is effectively same as directly running list1[0] = 5
However if you pass an immutable object into a function such as tuple, It will not support item assignement TypeError: 'tuple' object does not support item assignment. So you need to build a new immutable object and return it.
>>> tup1 = (1,2,3) # doing tup1[0] = 5 will cause TypeError
>>> tup2 = tup1 + (5,)
>>> tup2
(1, 2, 3, 5)
Put it in function,
>>> def func2(tup1):
return tup1 + (5,)
>>> func2(tup1=(1,2,3))
(1, 2, 3, 5)

How do I properly pass an argument to a function

I'm from a C++ background so this problem seems a little absurd to me:
Let's suppose I have a function:
def scale(data, factor):
for val in data:
val *= factor
This doesn't work as intended, if I pass a list, it changes nothing, but
def scale(data, factor):
for index, val in enumerate(data):
data[index] *= factor
and lst = [val * factor for val in lst] works properly.
How does Python handle argument passing? How do I know if the actual reference, or alias is passed?
if you want to mutate the list, you need to reference the elements. This version uses map (it could be written using list comprehensions)
def scale(data, factor):
return map(lambda x : x*factor, data)
a lambda function is an anonymous function.
>>> (lambda x : x + 1) (5)
6
The x takes the place of the variable in this case 5 + 1
So in this case, we traverse the list applying the function f(x) -> x * factor to every element of the list. The original list is not mutated, but instead we return a new version.
In python basic data types are passed by value - for example int, str, bool etc are passed by value
Derived data types like classes, enum, list, dict are passed by reference.
In your example, the problem is how you use the for loop - not the function argument. If you do:
for val in lst:
val += 1
The values inside lst won't get updated because the val is not the same as lst[0], lst[1] and so on IF val is of the basic data types. So, even here, the val is copied by value.
Second, In your example with enumerate:
But when you loop over the enumerated list, you are using data[index] - which modifies the element in the actual list.
And finally, In your example with the generator:
lst = [val * factor for val in lst] - here the generator loops over every element and creates a new list which is again stored in lst. This is something like a = a + 2 but extended to lists.
This behaviour is so because the basic data types are passed by value and the derived data types like lists are passed by reference consider this
>>> x = 24
>>> x + 1
25
>>> x
24
but on the otherhand with a list
>>> y = [1, 2, 3, 4]
>>> y.remove(2)
>>> y
[1,3,4]
so you should always be careful to reassign values back when performing operations on them in the case of the basic data ypes and also be careful with datatypes that are passed by reference because you could accidentally modify a variable without knowing

How not to change outer values?

I am new to Python, therefore my question will look like pretty foolish.
I try to make some program that makes two-dimensional array. One function puts items to list and returns an array. Then, second function put results of the first function and puts it in outer list.
My program looks like this:
def get_matrix():
d = some_dict
matrix = list()
while len(matrix)<3:
row = get_row(d)
matrix.append(row)
return matrix
def get_row(dict):
array = list()
for t in range(3):
a = dict.popitem()
array.append(a)
return array
some_dict = dict()
for x in range(9):
some_dict[x] = "a"+str(x)
print(some_dict)
print(get_matrix())
It works well. But what if I want not to change list d in outer function but just do it so:
def get_matrix():
d = some_dict
matrix = list()
while len(matrix)<3:
row = get_row(d)
matrix.append(row)
for x in row:
d.pop(x)
return matrix
In other words, I want to keep the whole dict in outer function.
Actually I want know why the outer values change if we change only the dict given by arguments of inner function?
You are popping items of the dict in the inner function. Since you are passing a handle to a mutable object (dict) to your get_row() function, the popping will effect the dict in the outer get_matrix as well.
Note that a dict as a complex object behaves differently in that regard than an immutable parameters (int, float, str, etc.). Their values remain unchanged.
You could pass a copy of your dict
row = get_row(copy.deepcopy(d))
as explained here: Understanding dict.copy() - shallow or deep?.
Btw, you can access the values in your dict without popping them
def get_row(dict):
...
for t in range(3):
...
# a = dict.popitem()
a = dict[t]
which will also leave the dict unharmed.

returning from recursive function in python

I am somewhat inexperienced with programming, and I am a little confused about how the return function works. I am trying to write a program that maps a function onto the elements of a nested list. The variable levels represents the number of times nested levels there are in the list. I currently can get the program to work by printing my final mapped list, totlist:
def map_nested(listbasket, function, levels): #listbasket is the list that contains lists
totlist=[] #this list will store the list after the function has been mapped to it
for listelement in listbasket:
if levels<=2: #once we get to the level that just contains a list of lists
newlist=list(map(function,listelement))
totlist.append(newlist) #add to final mapped list
else:
map_nested(listelement, function, levels-1) #recursively call for next level
print(totlist)
map_nested([[[1,2],[3,4]],[[5,6],[7,8]]], math.sqrt, 3) # my test function
Instead, I want something that returns the totlist, but I can't figure out how to do this. everytime I try returning it, it just returns an empty list or part of the list. I feel like i've tried every configuration of returns I can think of.
This will work:
import math
def map_nested(listbasket, function, levels): #listbasket is the list that contains lists
totlist=[] #this list will store the list after the function has been mapped to it
for listelement in listbasket:
if levels<=2: #once we get to the level that just contains a list of lists
newlist=list(map(function,listelement))
totlist.append(newlist) #add to final mapped list
else:
totlist.append(map_nested(listelement, function, levels-1))
return totlist
map_nested([[[1,2],[3,4]],[[5,6],[7,8]]], math.sqrt, 3) # my test function
or a slightly neater solution:
import math
def map_nested(input, function):
if type(input) is list:
return [map_nested(e, function) for e in input]
else:
return function(input)
print map_nested([[[1,2],[3,4]],[[5,6],[7,8]]], math.sqrt)
This is recursively applying the map_nested method to every list in your hierarchy. When the recursion reaches an element in a list, it applies the function provided in the original call.
Note that this works on arbitrarily deeply nested lists, and also works on unbalanced nested lists (e.g., [1, 2, [3, 4]]).
I would make totlist an argument:
def map_nested(listbasket, function, levels, totlist=None):
if totlist is None:
totlist = []
for listelement in listbasket:
if levels <= 2:
newlist = list(map(function, listelement))
totlist.append(newlist) #add to final mapped list
else:
map_nested(listelement, function, levels-1, totlist)
return totlist
Now:
>>> map_nested([[[1,2],[3,4]],[[5,6],[7,8]]], math.sqrt, 3)
[[1.0, 1.4142135623730951],
[1.7320508075688772, 2.0],
[2.23606797749979, 2.449489742783178],
[2.6457513110645907, 2.8284271247461903]]
If you want to simplify (not manually passing levels) and flatten the nest as you go, something like:
def map_nested_2(lst, f, out=None):
if out is None:
out = []
for item in lst:
if isinstance(item, list):
map_nested_2(item, f, out)
else:
out.append(f(item))
return out
Would give:
[1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979,
2.449489742783178, 2.6457513110645907, 2.8284271247461903]

How does Python iterate a for loop?

I tried the following code on Python, and this is what I got:
It seems like for many changes I try to make to the iterables by changing elem, it doesn't work.
lis = [1,2,3,4,5]
for elem in lis:
elem = 3
print lis
[1, 2, 3, 4, 5]
However if the iterables are objects with its own methods (like a list), they can be modified in a for loop.
lis = [[1],[2]]
for elem in lis:
elem.append(8)
print lis
[[1, 8], [2, 8]]
In the for loop what exactly is the 'elem' term? Thanks in advance!
The reason that this doesn't work is because you're misunderstanding what elem is. It's not the object itself, and it's not even correct to call it a "variable".
It's a name, kind of like a label, that points to the object. If you just directly assign over it, you're just overwriting the name to point at something else. But, you still have the original reference to the list, so assigning a different value over elem doesn't modify lis itself.
Now, in this case, since all of the objects that elem points to are integers, you can't even change them at all - because integers (and many other types, like strings or tuples) are immutable. That means, simply put, that once the object has been created it cannot be modified. It has nothing to do with whether they "have methods" or not (all Python objects have methods, integers included), but on whether or not they are immutable.
Some objects, however, are mutable, meaning that they can be changed. Lists are examples of such objects. In your second example, elem is a name that references the list objects contained within lis, which themselves are mutable. That is why modifying them in-place (using .append(), or .remove(), etc) works fine.
The elem variable in your for loop is a reference to the current object on each iteration. Changing it won't do anything; it will just change the value of the variable elem and that's going to be changed the next time through the loop anyway. To actually change the value of the element in the list, you need a reference to the list and the index of the element to be changed, and you don't have the latter.
So what you want to do is something like this:
for index, elem in enumerate(lis):
lis[index] = 3
This way you have elem for the element's value and index for the position in the list. It saves you from writing lis[index] constantly to get values, but you must still do so to change elements.
You can also do:
for index in xrange(len(lis)):
lis[index] = 3
However, in most situations this is considered un-Pythonic (among other things, what happens if the list gets longer or shorter while it's being iterated)?
Here, you are actually modifying the list object in your second example. In the first example, you are not modifying the number, you are replacing it. This can be a complicated nuance for new users of Python.
Check this out:
>>> x = 1
>>> id(x)
4351668456
>>> x = 2
>>> id(x)
4351668432
id returns the identifier of the object. As you can see above, the object of x changes both of these times.
>>> y = [1]
>>> id(y)
4353094216
>>> y.append(2)
>>> id(y)
4353094216
Here, I modify the list, so the list is still the original object y.
So, all this means that when you are doing elem = 3, it's not modifying it, it is replacing it. And by now, it's not associated with the list anymore.
This is one of the ways you could do what you are trying to do. This grabs the index and then modifies the list, not the number.
lis = [1,2,3,4,5]
for idx, elem in enumerate(lis):
lis[idx] = 3
print lis
[1, 2, 3, 4, 5]
When you assign a new value to the name elem, you just change the local binding in the for loop. If you want to change the values stored in lis, use map or a list comprehension, like this:
lis = [3 for elem in lis]
You can, however, modify the attributes of elem (or call methods that do so), just like you can on any other value.
In your first example you are trying to modify an integer, and it's inmutable (as strings are).
Python variables should be seen as labels pointing to an object. When you iterate over a list of inmutables, elem points to an inmutable object, not to that position in the list, so you can't modify the original list.
In the second case, elem point to an object that can be modified, so you see the original list changed.
It depends on what the type() of elem is.
In your first case each elem is an int object and it does work to change it. You are changing a temporary object when you say: elem = 3, not the item in the list itself.
In the second case each elem is a list object.

Categories