4

I wrote the following code to check if integers are passed by value or reference.

foo = 1

def f(bar):
    print id(foo) == id(bar)
    bar += 1
    print foo, bar

f(foo)

The output I get is

True
1, 2

From the Python documentation, id(object) returns the identity of an object. In the CPython implementation, this is the address of the object in memory. Since the first statement in the function body returns True, it means foo has been passed by reference, but why does the last statement print 1, 2 instead of 2, 2?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ajay
  • 9,402
  • 8
  • 44
  • 71
  • 7
    In Python everything is passed by value. – kennytm Oct 16 '13 at 09:01
  • Because integers are immutable. – Ashwini Chaudhary Oct 16 '13 at 09:01
  • possible duplicate of [Python: How do I pass a variable by reference?](http://stackoverflow.com/questions/986006/python-how-do-i-pass-a-variable-by-reference) – Lasse V. Karlsen Oct 16 '13 at 09:01
  • Although the linked duplicate question is worded different, it answers this question as well. All values in Python are passed by value. – Lasse V. Karlsen Oct 16 '13 at 09:02
  • 1
    @hcwhsa Integers are not passed by value because they are immutable. Integers (and all other values) in Python are always passed by value. You can, however, pass *references*, which is different. In other words, you can pass references by value. – Lasse V. Karlsen Oct 16 '13 at 09:03
  • @KennyTM why does id(foo) == id(bar) is True, then? Don't they refer to the same object? I still don't understand the output. – ajay Oct 16 '13 at 09:05
  • Because apparently `id(number)` evaluates to a stable value. Meaning `id(foo) == id(bar)` because you're actually doing `id(1) == id(1)`. Try replacing either `foo` or `bar` in that statement with the number `1` and it will still output `True`. `id` does not have some magic way of obtaining the *variable* in use, it can only work on what is given to it, which in this case is a number. – Lasse V. Karlsen Oct 16 '13 at 09:08
  • 3
    @ajay `f(foo)` is actually equivalent to `bar = foo`, i.e it creates a new reference to the same object, that's why `foo is bar` is `True`. But `+=` operation(and all operations) on integers always returns a new integer, i.e after `bar += 1`, `bar` will refer to a new object and `foo` still refers to the same old object which is still unchanged. – Ashwini Chaudhary Oct 16 '13 at 09:10

4 Answers4

5

In Python, like in many modern OO languages

foo = 1

actually creates an object with the value 1 and assigns a reference to the alias foo. The internal type of foo is PyIntObject. This means Python isn't using the CPU / hardware int type, it always uses objects to handle numbers internally. The correct term is "plain integer", btw.

But creating objects is very expensive. That's why Python keeps an internal cache for a few numbers. Which means:

foo = 1
bar = 1
assert id(foo) == id(bar)

This isn't guaranteed, it's just a side effect of the implementation.

Number types in Python are also immutable. So even though bar in your example is an alias for the cached int number, changing bar doesn't modify the internal value. Instead, bar is pointed to another instance which is why the id changes.

Because of the aforementioned optimization, this works as well:

foo = 1
bar = 1
assert id(foo) == id(bar)

bar += 1
assert id(foo) != id(bar)

bar -= 1
assert id(foo) == id(bar)
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 1
    -1 The interning of small integers is interesting, and another reason to not look too closely at `id()`s, but not at all relevant for OP's snippet. –  Oct 16 '13 at 09:28
  • 1
    Does it automatically intern numbers on use? In other words, when will `id(x) != id(x)` be `False`? – Lasse V. Karlsen Oct 16 '13 at 09:30
  • @delnan: Since my answer is 100% relevant, I can only assume that you didn't understand the question at all. Next time when you downvote, maybe give a reason or an argument *why* you think it's not relevant. – Aaron Digulla Oct 16 '13 at 09:31
  • @LasseV.Karlsen: I'm not sure. [Java documents this](http://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html#valueOf(int)) but I couldn't find anything quickly in the Python docs. Maybe ask a new question? – Aaron Digulla Oct 16 '13 at 09:32
  • 1
    @LasseV.Karlsen: I agree with delnen here that you can't rely in the behavior of `id()`. In general if you feel like you should use `id()`, maybe your design is broken. – Aaron Digulla Oct 16 '13 at 09:34
  • No, you misunderstood, I'm only curious about the interning part :) I know full well how Python parameter passing works because it's the same way that C#/.NET works, except that in .NET you can opt in for pass-by-reference, which you can't do in Python, but I have answered similar questions here for C#/.NET, which is why I know how this really works. I was really just trying to get some information on how number interning works, nothing else :) – Lasse V. Karlsen Oct 16 '13 at 09:35
  • 1
    It's not relevant because no integer in OP's example is re-used from the internal cache: 1 occurs once (a reference to it is passed to the function, but nothing attempts to copy the 1 and gets the same reference from the cache), 2 occurs once. –  Oct 16 '13 at 09:36
  • @delnan: To understand the "odd" behavior you need to know everything that I explained in my question. I feel you don't understand that answering questions often implies widening the view of the person asking.$ – Aaron Digulla Oct 16 '13 at 09:39
  • Yes, but if you increase `foo` and `bar` both by `1`, `id(foo) == id(bar)` will still return `True` meaning that although you now have two objects in memory, both with the value 1, they still generate the same id. I am saying this by observation, I do not know the mechanisms here. I was just curious about what really happens when `bar += 1` is executed. Does a new object get allocated, given the value of `2` (in this case), or is an existing object with the value `2` reused? This is completely besides the question of the OP however, it's just my curiousity :) – Lasse V. Karlsen Oct 16 '13 at 09:39
  • @LasseV.Karlsen: It is implementation defined. I believe CPython "precreates" small integers in the range ~ [-128,128] or something like that, anything else gets its own object. – Nico Oct 16 '13 at 09:44
  • 1
    @AaronDigulla If you had no interning, the behaviour of the OP's program was exactly the same: `foo` and `bar` are the same at the start, and after `+= 1`, they are different. There is no `foo = 1; bar = 1`, but an (implicit) `bar = foo`. That makes the stuff about interning irrelevant. – glglgl Oct 16 '13 at 09:52
3

There seems to be a lot of confusion around this "issue". Variables names in Python are actually all references to objects. Assignements to variable names aren't actually changing the objects themselves, but setting the reference to a new object. So in your case:

foo = 1 #

def test(bar):
    # At this point, "bar" points to the same object as foo.
    bar = 2    # We're updating the name "bar" to point an object "int(2)".
    # 'foo' still points to its original object, "int(1)".
    print foo, bar # Therefore we're showing two different things.

test(foo)

The way Python's syntax resembles C and the fact many things are syntactic sugar can be confusing. Remembering that integer objects are acually immutable, and it seems weird that foo += 1 could be a valid statement. In actuality, foo += 1 is actually equivalent to foo = foo + 1, both of which translate to foo = foo.__add__(1), which actually returns a new object, as shown here:

>>> a = 1
>>> id (a)
18613048
>>> a += 1
>>> id(a)
18613024
>>>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nico
  • 1,328
  • 8
  • 7
1

The following happens:

print id(foo) == id(bar)

The identity is the same. print foo is bar would have yielded the same, BTW.

bar += 1

This is translated to:

bar = bar.__iadd__(1)

And only if this does not work or does not exist, it calls:

bar = bar.__add__(1)

(I omit the case that bar = 1.__radd__(bar) could as well be called.)

As bar refers to a number, which is immutable, a different object is returned instead, so that bar refers to 2 now, leaving foo untouched.

If you do any of

print id(foo) == id(bar)
print foo is bar

now, you see that they now point to different objects.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
glglgl
  • 89,107
  • 13
  • 149
  • 217
0

In Python 2, the current implementation keeps an array of integer objects for all integers between -5 and 256. So if you assign a variable as var1 = 1 and some other variable as var2 = 1, they are both pointing to the same object.

Python "variables" are labels that point to objects, rather than containers that can be filled with data, and thus on reassignment, your label is pointing to a new object (rather than the original object containing the new data). See Stack Overflow question Python identity: Multiple personality disorder, need code shrink.

Coming to your code, I have introduced a couple more print statements, which will display that the variables are being passed by value

foo = 1
def f(bar):
    print id(foo) == id(bar)
    print id(1), id(foo), id(bar) #All three are same
    print foo, bar 
    bar += 1
    print id(bar), id(2)
    print foo, bar

f(foo)
Community
  • 1
  • 1
Anshul Goyal
  • 73,278
  • 37
  • 149
  • 186
  • 1
    This nicely explains interning, which is not relevant in this case at all. The implicit `bar = foo` at the calling time happens as well with non-interned numbers such as `123432`. – glglgl Oct 16 '13 at 09:56