It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
Here's some code:
li=[1,2,3]
def change(l):
l[2]=10
change(li)
print li
[1, 2, 10]
But I want this:
li=[1,2,3]
def change(l):
l=[1,2,10]
change(li)
print li
[1,2,3]
For some reason,I have to change whole list in method,how can I achieve this?Anything wrong or my mistake?
When you want to change the entire list inside a method, you'll probably want to create a copy and return it:
def change(li):
new_list = li[:] #copy
new_list[2] = 10
return new_list
li = [1,2,3]
new_lst = change(li) #new_lst = [1,2,10]
If you don't want to return the list, but want to modify in place, you can use slice assignment (*warning: This is not common practice):
def change(li):
li[:] = [1,2,10]
li = [4,5,6]
change(li)
print(li) #[1, 2, 10]
Like this:
li = [1, 2, 3]
def change(l):
l[:] = [1, 2, 10]
change(li)
print li # [1, 2, 10]
The reason your approach does not work is that in python, variables are simply names that reference objects. When you write l = [1, 2, 10] you're re-binding the name l to refer to the new list you've just created, the old list is unchanged and still referred to by the global variable li.
The above code instead modifies the object pointed to by l by using slice assignment.
As mgilson indicates in his answer, you should make it very clear that the function actually modifies the passed argument in-place. If not, an unsuspecting programmer might pass it a list he intends to use as-is later, only to discover that everything in it has been lost. Giving your function a name indicating modification (like you've done) and not returning anything from it are both common indicators of such functions, like for instance random.shuffle.
To achieve the same effect for a dict, the documentation tells us that this will do:
def change_dict(d):
d.clear() # Empty dict
d.update({"a": 3, "b": 9})
When you do the following:
def change(l):
l=[1,2,10]
You are actually changing which list l points to. You need to change the list instance passed in to change. You can append to it, you can pop from it, etc and your changes will be made to the list you passed in. If you change your change function to what I have, your example will work.
def change(l):
l[:] = [1,2,10]
Using aliasing
Documentation: List Aliasing — How to Think like a Computer Scientist (2nd editon)
You can take advantage of Python's list aliasing (which is common gotcha for beginners).
When you pass li to the change function, l aliases to li, i.e., they both point to the same object in memory and changing one changes the other. But when you do l = [1, 2, 10], l is pointing to another list and you lose the aliasing magic. To resolve that, you can use the slice operation to replace to full list as so:
li = [1, 2, 3]
def change(l):
l[:] = [1, 2, 10] # doesn't make a new list
change(li) # preserves aliasing
print li # returns [1, 2, 10]
Using globals
Documentation: The global statement — Python Docs
The changes you're making to l inside the function are not applied to the li list outside. You can use a global to affect the li list you're trying to change:
li = [1, 2, 3]
def change():
global li
li = [1, 2, 10] # use this
li[2] = 10 # or this, up to you
change()
print li # returns [1, 2, 10]
In this code the l is work as common object to whole so you can check by the id() that provide the object memory location.
so that's why you got the last output [1,2,10]
l = [1, 2, 3]
print id(l)
def change(l):
l[:] = [1, 2, 10]
print id(l)
change(l)
print l
print id(l)
if you want your desired output than you use the deepcopy that provide the diffident object to function.
from copy import deepcopy
l = [1, 2, 3]
print id(l)
def change(l):
l[:] = [1, 2, 10]
print id(l)
change(deepcopy(l))
print l
print id(l)
Related
I'm new to writing code, and I understand the behavior of lists to an extent. Whenever a list is modified within a function's scope, the one in global scope changes too.
For example,
def modify_list(lst):
lst.append(5)
lst = [1, 2, 3, 4]
#This output is [1, 2, 3, 4]
print(lst)
modify_list(lst)
#This output is [1, 2, 3, 4, 5] because of the function.
print(lst)
I don't understand why this example won't work:
def modify_list(lst):
lst = [1, 2, 3, 4, 5]
lst = [1, 2, 3, 4]
#Output is [1, 2, 3, 4]
print(lst)
modify_list(lst)
#Output is [1, 2, 3, 4]
print(lst)
Why doesn't lst get modified in the second example? Is it because I'm creating a new object within the function's scope? Using the global keyword works instead of passing a parameter, but I want to avoid using global unless absolutely necessary.
I'm using this in an initialization function and want to revert the list back to its original state whenever the function is called. Again, using global works, I'm just wondering why this doesn't work.
Thanks! (Sorry if I'm not good at explaining things well)
The id function comes in handy here. Basically, the id function returns an integer that is guaranteed to be unique and constant for the lifetime of whatever object it was called on. In fact, in CPython (regular Python), id returns the address of the object in memory.
So if you run your code, printing the id of lst before and after running your modify_list, you'll find that the id changes when you assign to lst but not when you append.
Calling append on lst won't change the id of lst because lists in Python are mutable, and appending simply mutates the list. But, when you assign [1, 2, 3, 4, 5] to lst, you are creating a brand-new list object and assigning it to lst. This is not a mutation, and doesn't change the original in anyway. In general in Python, you can mutate arguments within a function to modify the original copy, but assigning a new object to it is not a mutation and won't change the original copy.
In
def modify_list(lst):
lst = [1, 2, 3, 4, 5]
you make local variable lst point to a completely different object than the one you passed as the argument in modify_list(lst) call. Maybe this article will help you understand: https://medium.com/school-of-code/passing-by-assignment-in-python-7c829a2df10a
I have this function to simulate reverse() in python:
My_list = [[1, 3, "s"], 2, 2, 3, 4, 2]
def listRev(list):
list=list[::-1]
print list
listRev(My_list)
print My_list
The function gives me correct reversed list but I expect when I print My_list in the last line, the code print reversed My_list not My_list itself. How can I solve my problem?
If you want to simulate reverse, you must modify the list passed as a parameter to you function, not simply use a new list in your function.
When you write list=list[::-1] the local copy of the original parameter points to a new list. You should do instead :
def rev(l):
l[:] = l[::-1]
print (l)
The usage of l[:] asks Python to replace the content of the list, and not to make the variable point to a new list.
This can be solved by returning a list from the function
My_list = [[1, 3, "s"], 2, 2, 3, 4, 2]
def listRev(list):
list=list[::-1]
print list
return list
My_list = listRev(My_list)
print My_list
This is because lists are not passed by reference in Python.
Post Comment Edit
If you want the function to be in place only for My_list even though it is very wrong and bad programming to do so, you can use this
My_list = [[1, 3, "s"], 2, 2, 3, 4, 2]
def listRev():
global My_list
My_list = My_list[::-1]
listRev()
print My_list
I understand that in Python regular c++ style variable assignment is replaced by references to stuff ie
a=[1,2,3]
b=a
a.append(4)
print(b) #gives [1,2,3,4]
print(a) #gives [1,2,3,4]
but I'm still confused why an analogous situation with basic types eg. integers works differently?
a=1
b=a
a+=1
print(b) # gives 1
print(a) # gives 2
But wait, it gets even more confusing when we consider loops!
li=[1,2,3]
for x in li:
x+=1
print(li) #gives [1,2,3]
Which is what I expected, but what happens if we do:
a,b,c=1,2,3
li=[a,b,c]
for x in li:
x+=1
print(li) #gives [1,2,3]
Maybe my question should be how to loop over a list of integers and change them without map() as i need a if statement in there. The only thing I can come up short of using
for x in range(len(li)):
Do stuff to li[x]
is packaging the integers in one element list. But there must be a better way.
Well, you need to think of mutable and immutable type.
For a list, it's mutable.
For a integer, it's immutable, which means you will refer to a new object if you change it. When a+=1 is executed, a will be assigned a new object, but b is still refer to the same one.
a=[1,2,3]
b=a
a.append(4)
print(b) #[1,2,3,4]
print(a) #[1,2,3,4]
Here you are modifying the list. The list content changes, but the list identity remains.
a=1
b=a
a+=1
This, however, is a reassignment. You assign a different object to a.
Note that if you did a += [4] in the 1st example, you would have seen the same result. This comes from the fact that a += something is the same as a = a.__iadd__(something), with a fallback to a = a.__add__(something) if __iadd__() doesn't exist.
The difference is that __iadd__() tries to do its job "inplace", by modifying the object it works on and returning it. So a refers to the same as before. This only works with mutable objects such as lists.
On immutable objects such as ints __add__() is called. It returns a different object, which leads to a pointing to another object than before. There is no other choice, as ints are immutable.
a,b,c=1,2,3
li=[a,b,c]
for x in li:
x+=1
print(li) #[1,2,3]
Here x += 1 means the same as x = x + 1. It changes where x refers to, but not the list contents.
Maybe my question should be how to loop over a list of integers and change them without >map() as i need a if statement in there.
for i, x in enumerate(li):
li[i] = x + 1
assigns to every list position the old value + 1.
The important thing here are the variable names. They really are just keys to a dictionary. They are resolved at runtime, depending on the current scope.
Let's have a look what names you access in your code. The locals function helps us: It shows the names in the local scope (and their value). Here's your code, with some debugging output:
a = [1, 2, 3] # a is bound
print(locals())
for x in a: # a is read, and for each iteration x is bound
x = x + 3 # x is read, the value increased and then bound to x again
print(locals())
print(locals())
print(x)
(Note I expanded x += 3 to x = x + 3 to increase visibility for the name accesses - read and write.)
First, you bind the list [1, 2, 3]to the name a. Then, you iterate over the list. During each iteration, the value is bound to the name x in the current scope. Your assignment then assigns another value to x.
Here's the output
{'a': [1, 2, 3]}
{'a': [1, 2, 3], 'x': 4}
{'a': [1, 2, 3], 'x': 5}
{'a': [1, 2, 3], 'x': 6}
{'a': [1, 2, 3], 'x': 6}
6
At no point you're accessing a, the list, and thus will never modify it.
To fix your problem, I'd use the enumerate function to get the index along with the value and then access the list using the name a to change it.
for idx, x in enumerate(a):
a[idx] = x + 3
print(a)
Output:
[4, 5, 6]
Note you might want to wrap those examples in a function, to avoid the cluttered global namespace.
For more about scopes, read the chapter in the Python tutorial. To further investigate that, use the globals function to see the names of the global namespace. (Not to be confused with the global keyword, note the missing 's'.)
Have fun!
For a C++-head it easiest tho think that every Python object is a pointer. When you write a = [1, 2, 3] you essentially write List * a = new List(1, 2, 3). When you write a = b, you essentially write List * b = a.
But when you take out actual items from the lists, these items happen to be numbers. Numbers are immutable; holding a pointer to an immutable object is about as good as holding this object by value.
So your for x in a: x += 1 is essentially
for (int x, it = a.iterator(); it->hasMore(); x=it.next()) {
x+=1; // the generated sum is silently discarded
}
which obviously has no effect.
If list elements were mutable objects you could mutate them exactly the way you wrote. See:
a = [[1], [2], [3]] # list of lists
for x in a: # x iterates over each sub-list
x.append(10)
print a # prints [[1, 10], [2, 10], [3, 10]]
But unless you have a compelling reason (e.g. a list of millions of objects under heavy memory load) you are better off making a copy of the list, applying a transformation and optionally a filter. This is easily done with a list comprehension:
a = [1, 2, 3, 0]
b = [n + 1 for n in a] # [2, 3, 4, 1]
c = [n * 10 for n in a if n < 3] # [10, 20, 0]
Either that, or you can write an explicit loop that creates another list:
source = [1, 2, 3]
target = []
for n in source:
n1 = <many lines of code involving n>
target.append(n1)
Your question has multiple parts, so it's going to be hard for one answer to cover all of them. glglgl has done a great job on most of it, but your final question is still unexplained:
Maybe my question should be how to loop over a list of integers and change them without map() as i need a if statement in there
"I need an if statement in there" doesn't mean you can't use map.
First, if you want the if to select which values you want to keep, map has a good friend named filter that does exactly that. For example, to keep only the odd numbers, but add one to each of them, you could do this:
>>> a = [1, 2, 3, 4, 5]
>>> b = []
>>> for x in a:
... if x%2:
... b.append(x+1)
Or just this:
>>> b = map(lambda x: x+1, filter(lambda x: x%2, a))
If, on the other hand, you want the if to control the expression itself—e.g., to add 1 to the odd numbers but leave the even ones alone, you can use an if expression the same way you'd use an if statement:
>>> for x in a:
... if x%2:
... b.append(x+1)
... else:
... b.append(x)
>>> b = map(lambda x: x+1 if x%2 else x, a)
Second, comprehensions are basically equivalent to map and filter, but with expressions instead of functions. If your expression would just be "call this function", then use map or filter. If your function would just be a lambda to "evaluate this expression", then use a comprehension. The above two examples get more readable this way:
>>> b = [x+1 for x in a if x%2]
>>> b = [x+1 if x%2 else x for x in a]
You can do something like this: li = [x+1 for x in li]
Simple example:
myList = [1, 2, 3, 4, 5]
for obj in myList:
obj += 1
print myList
prints
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
while:
myList = [1, 2, 3, 4, 5]
for index in range(0,len(myList)):
myList[index] += 1
print myList
prints
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
Conclusion:
Lists can be modified in place using global list access Lists can
List items can NOT be modified in place using the iterator object
All example code I can find uses the global list accessors to modify the list inplace.
Is it so evil to modify a list iterator?
The reason obj += 1 does not do what you expect is that this statement does not modify obj in-place. Instead, it computes the new value, and rebinds the variable obj to point to the new value. This means that the contents of the list remain unchanged.
In general it is possible to modify the list while iterating over it using for obj in myList. For example:
myList = [[1], [2], [3], [4], [5]]
for obj in myList:
obj[0] += 1
print(myList)
This prints out:
[[2], [3], [4], [5], [6]]
The difference between this and your first example is that here, the list contains mutable objects, and the code modifies those objects in-place.
Note that one could also write the loop using a list comprehension:
myList = [val+1 for val in myList]
I think you've misunderstood what an "iterator object" is. A for loop is not an iterator object. For all intents and purposes, a for loop like this:
myList = [0, 1, 2, 3, 4]
for x in myList:
print x
does this (but more efficiently and less verbosely):
i = 0
while i < len(myList)
x = myList[i]
print x
i += 1
So you see, any changes made to x are lost as soon as the next loop starts, because the value of x is overwritten by the value of the next item in the list.
As others have observed, it is possible to alter the value of a list while iterating over it. (But don't change its length! That's where you get into trouble.) One elegant way to do so is as follows:
for i, x in enumerate(myList):
myList[i] = some_func(x)
Update: It's also important to understand that no copying goes on in a for loop. In the above example, i and x -- like all variables in Python -- are more like pointers in C/C++. As the for loop progresses, obj points at myList[0], myList[1], etc, in turn. And like a C/C++ pointer, the properties of the object pointed to are not changed when the pointer is changed. But also like a C pointer, you can directly modify the thing pointed at, because it's not a copy. In C, this is done by dereferencing the pointer; in Python, this is done by using a mutable object. That's why NPE's answer works. If i and x were even shallow copies, it wouldn't be possible to do what he does.
The reason you can't directly change ints the way you can change lists (as in NPE's answer), is that ints aren't mutable. Once a 5 object is created, nothing can change its value. That's why passing around a pointer to 5 is safe in Python -- no side-effects can occur, because the thing pointed to is immutable.
in for obj in myList:, in every iteration, obj is a (shallow) copy of the element in myList. So the change on the obj does nothing to myList's elements.
It's different with the Perl for my $obj (#myList) {}
You are confused. Consider your first snippet:
myList = [1, 2, 3, 4, 5]
for obj in myList:
obj += 1
print a
obj is not some kind of magical pointer into the list. It is a variable which holds a reference to an object which happens to also be in myList. obj += 1 has the effect of increasing the value stored in obj. Your code then does nothing with that value.
To be clear: There are no copies in this code example. obj is a variable, which holds an object in the list. That is all.
In the first example the integer is copied into obj which is increased by 1.
The list is not changed.
If you would use a class instance and perform operations on it, it would be changed.
Modification in list is allowed. Your code examples arbove are pretty garbled...
myList = [1, 2, 3, 4, 5]
for index in range(0,len(myList)):
myList[index] += 1
print myList
This works.
What's the difference between
lst = range(100)
and
lst[:] = range(100)
Before that assignment the lst variable was already assigned to a list:
lst = [1, 2, 3]
lst = range(100)
or
lst = [1, 2, 3]
lst[:] = range(100)
When you do
lst = anything
You're pointing the name lst at an object. It doesn't change the old object lst used to point to in any way, though if nothing else pointed to that object its reference count will drop to zero and it will get deleted.
When you do
lst[:] = whatever
You're iterating over whatever, creating an intermediate tuple, and assigning each item of the tuple to an index in the already existing lst object. That means if multiple names point to the same object, you will see the change reflected when you reference any of the names, just as if you use append or extend or any of the other in-place operations.
An example of the difference:
>>> lst = range(1, 4)
>>> id(lst)
74339392
>>> lst = [1, 2, 3]
>>> id(lst) # different; you pointed lst at a new object
73087936
>>> lst[:] = range(1, 4)
>>> id(lst) # the same, you iterated over the list returned by range
73087936
>>> lst = xrange(1, 4)
>>> lst
xrange(1, 4) # not a list, an xrange object
>>> id(lst) # and different
73955976
>>> lst = [1, 2, 3]
>>> id(lst) # again different
73105320
>>> lst[:] = xrange(1, 4) # this gets read temporarily into a tuple
>>> id(lst) # the same, because you iterated over the xrange
73105320
>>> lst # and still a list
[1, 2, 3]
When it comes to speed, slice assignment is slower. See Python Slice Assignment Memory Usage for more information about its memory usage.
The first one redefines the built-in name list to point to some list.
The second fails with TypeError: 'type' object does not support item assignment.
list[:] will only work if there is already an object named list that allows slice assignment.
Also, you shouldn't name variables list because there is a built-in named list which is the list type itself.
list[:] specifies a range within the list, in this case it defines the complete range of the list, i.e. the whole list and changes them. list=range(100), on the other hand, kind of wipes out the original contents of list and sets the new contents.
But try the following:
a=[1,2,3,4]
a[0:2]=[5,6]
a # prints [5,6,3,4]
You see, we changed the first two elements with the assignment. This means, using this notation, you can change several elements in the list once.
[:] is also useful to make a deep copy of the list.
def x(l):
f=l[:]
g=l
l.append(8)
print "l", l
print "g", g
print "f", f
l = range(3)
print l
#[0, 1, 2]
x(l)
#l [0, 1, 2, 8]
#g [0, 1, 2, 8]
#f [0, 1, 2]
print l
#[0, 1, 2, 8]
Modification to l is get reflected in g (because, both point to same list, in fact, both g and l are just names in python), not in f(because, it's a copy of l)
But, in your case, It doesn't make any difference. (Though, I'm not eligible to comment on any memory usage of both methods.)
Edit
h = range(3)
id(h) #141312204
h[:]=range(3)
id(h) #141312204
h=range(3)
id(h) #141312588
list[:] = range(100) updates the list
list = range(100) creates new list.
#agf: thanks for pointing my error
list[:] = range(100)
won't work on uninitialized variable, as it is modifying it. The [:] specifies the whole list/touple.