6

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.

eyquem
  • 26,771
  • 7
  • 38
  • 46
Michal
  • 1,300
  • 2
  • 14
  • 22
  • 1
    `b=a` is a resource reference. So it will affect the same memory, thus it stays linked. The `for` loop you do is a new instance (`x`) and will thus not affect the `li` unless you write the modification back to the list. –  Jan 03 '14 at 22:15
  • Maybe this link: http://stackoverflow.com/questions/4081217/how-to-modify-list-entries-during-for-loop can clarify you aoome things regarding this topic. – Xar Jan 03 '14 at 22:19

6 Answers6

7

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.

chinuy
  • 145
  • 1
  • 12
6
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.

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • This is important... in C, there are pointers and references, but _never_ really an 'object'. In python, there are objects, and there are names for those objects, and they don't line up exactly to C-style references or pointers. – Corley Brigman Jan 03 '14 at 22:36
  • @CorleyBrigman JFYI, indeed there are objects in C (read the standard), but usually not in a OOP way. But you can do OOP in C as well, with function pointers and the like. – glglgl Jan 04 '14 at 07:11
3

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!

mknecht
  • 1,205
  • 11
  • 20
  • print(locals()) output is much more than what you have posted, any idea how to extract what you posted only ) (i.e. last two value.keys a,x for a dict that is unordered by definition ?) – pippo1980 Sep 14 '22 at 07:01
2

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)
9000
  • 39,899
  • 9
  • 66
  • 104
  • Even numbers are "pointers" in Python. The fact that they're pointers to immutable objects doesn't mean they're not pointers—after all, the same is true of, say, tuples. – abarnert Jan 03 '14 at 22:38
  • @abarnert: I know :) I can imagine that for arithmetic calculations they can be represented as plain numbers, and only "boxed" when actual object properties are required (hashing, for instance, or overloaded operations). I'm no expert in CPyhton internals, though. so maybe this is not the case. I've edited my answer, though. – 9000 Jan 03 '14 at 22:46
  • It's definitely not the case in CPython. It might be the case in Jython, and you could end up with something equivalent after JIT-compiling with PyPy or IronPython. But with CPython, even evaluating `1+2` looks up the `np_add` slot on the first `PyLongObject *` and calls it with the other `PyLongObject *` as an argument. (It's actually even more complicated than that, but that's enough to give you the idea. There is no unboxing.) – abarnert Jan 03 '14 at 23:07
  • Anyway, your edited version still says "every Python object is a pointer, except for numbers", which is still not true. Every Python object is a pointer, including numbers. Some Python objects are pointers to immutable objects, including everything from numbers to tuples, but that's a separate issue, which you already address nicely in the next paragraph. – abarnert Jan 03 '14 at 23:08
  • @abarnert: Oops, fixed. – 9000 Jan 04 '14 at 08:20
  • Nice explanation after the edits. I think glglgl already covers all the important stuff from your answer, but you cover some of it from a different angle that might be helpful to people coming from C-family languages. – abarnert Jan 05 '14 at 03:30
1

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]
abarnert
  • 354,177
  • 51
  • 601
  • 671
0

You can do something like this: li = [x+1 for x in li]

misakm
  • 122
  • 6