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.
Related
I think that the variable length argument turns into a tuple when it enters the edit_list function, so I changed it from a Tuple to a List to edit it. When it returns, I assume it is still treated as a Tuple and therefore no changes to the argument values can be returned?
If so, how would I go about editing the contents of a list that is used in a variable length argument context?
def main():
x = ['hi','hello','world',1,2]
edit_list(*x)
print(x)
#why can't I change the list index 1 value to '2' and then
#return the modified arg list to main and then print it out?
def edit_list(*args):
args = list(args)
print(args)
args[1] = 2
return args
if __name__ == '__main__' : main()
You would need to pass in the list directly, instead of unpacking the list using edit_list(*x).
def edit_list(my_list):
my_list[1] = 2
def main():
x = [ ... ]
edit_list(x)
print(x)
To understand the mechanism of this, you should try to be familiar with the concept "mutable" and "immutable". (of course, only if you want to be better at Python).
Take your code as an example, if the element you passed in is a list, and you are changing the element of that list, you will get a different result.
def main():
x = ['hi',['hello'],'world',1,2]
edit_list(*x)
print(x)
# After you run edit_list, the original x will be changed
def edit_list(*args):
args = list(args)
print(args)
args[1][0] = 2
return args
In python, objects are either "mutable" or "immutable". lists for example, are mutable. integers, strings are immutable. When you pass a mutable object to a function, you pass in it's reference so when you edit it(not assigning another object to the variable) you will edit the original object. However, if it's immutable, when you pass in the object to the function, it will generate a copy.
Also use your code as an example. You are effectively doing
edit_list("hi", "hello", "world", 1, 2)
All the arguments are immutable, so you copied each of them and give them to the function. Therefore, when you are inside the function, you already have different objects to the original. There's nothing you can do to change the original list.
However, if you pass them in as a list
edit_list(x)
Because x is a mutable object, you pass the reference, or the original object in the function. So when you edit the list lst[1] = 2, the original one will change. However, if you do something like lst = [1, 2, 3], the original x won't be changed, because you just created another object and assigned to the unrelated variable lst.
I have a list of objects from a created class. From this list I need to create a sublist of the objects, which share the same minimum value, where value is a property of the class.
For now, I am using this algorithm:
# find the indices of the objects with min value
values = []
for object in objects:
values.append(object.value)
values = np.array(values)
indices_of_objects_with_min_values = np.where(values == values.min())[0]
# create sub list with indices
objects_with_min_value = []
for index in indices_of_objects_with_min_values:
objects_with_min_value.append(objects[index])
I think there must be a more pythonic way, than using two for-loops and converting my value-list to a numpy.array()
What can I do, to make this algorithm better, so maybe that I don't need to use any for-loops. I think there could be a way with a list comprehension, but i don't know how to do this.
You can do in one-line
[obj for obj in objects if obj.value == min(objects, key = lambda x: x.value).value]
This line retrieves all objects whose value is equal to the min of all values.
Better even is to save the minimum value before, and then use list comprehension:
#Get minimum value
minValue = min(objects, key = lambda x: x.value).value
#Get all objects whose value is equal to the minimum value
[obj for obj in objects if obj.value == minValue]
This way you don't recompute the minimum value for every item.
You can use filter and enumerate
class Foo:
def __init__(self, value):
self.value = value
f1, f2, f3 = map(Foo, (1,1,3))
minval = min(getattr(x, 'value') for x in (f1, f2, f3))
objs =list(filter(lambda x: x[1].value==minval, enumerate((f1, f2, f3))))
objs will be a list of tuples with two elements, the first being the index, the second the instance.
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.
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
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]