P i
P i

Reputation: 30734

In Python, why doesn't 'y = x; y += 1' also increment x?

First create a function for displaying reference count (note that we have to -1 each time to get the correct value, as the function itself INCREF-s the argument)

>>> from sys import getrefcount as rc
>>> x=1.1
>>> rc(x)-1
1

Now make another reference to the same PyObject:

>>> y=x

>>> rc(x)-1
2
>>> rc(y)-1
2

>>> x is y
True

Now perform an operation on the second handle, y:

>>> y+=1

This should be invoking PyNumber_InPlaceAdd on the PyObject that y points to.

So if this is true I would be expecting x to also read 2.1

>>> x,y
(1.1, 2.1)

>>> x is y
False

>>> rc(x)-1
1
>>> rc(y)-1
1

So my question is, what is Python doing internally to provide the right behaviour, rather than the behaviour I would expect from looking at PyNumber_InPlaceAdd?

(Note: I am using 1.1; if I used 1 the initial reference count would be >300, because 1 must be used all over the place behind-the-scenes in CPython, and it is clever enough to reuse objects.)

(This also begs the question: if I have foo = 20; bar = 19; bar += 1 does this mean it has to look through all its objects and check whether there already exists an object with this value, and if so reuse it? A simple test shows that the answer is no. Which is good news. It would be horribly slow once the program size gets big. So Python must just optimise for small integers.)

Upvotes: 1

Views: 133

Answers (2)

jonrsharpe
jonrsharpe

Reputation: 122143

You don't need getrefcount for this, you can just use id:

>>> x = 1.1
>>> id(x)
50107888
>>> y = x
>>> id(y)
50107888 # same object
>>> y += 1
>>> id(y)
40186896 # different object
>>> id(x)
50107888 # no change there

float objects (along with e.g. str and int) are immutable in Python, they cannot be changed in-place. The addition operation therefore creates a new object, with the new value, and assigns it to the name y, effectively:

temp = y + 1
y = temp

In CPython, integers from -5 to 256 inclusive are "interned", i.e. stored for reuse, such that any operation with the result e.g. 1 will give a reference to the same object. This saves memory compared to creating new objects for these frequently-used values each time they're needed. You're right that it would be a pain to search all existing objects for a match every time a new object might be needed, so this is only done over a limited range. Using a contiguous range also means that the "search" is really just an offset in an array.

Upvotes: 4

glglgl
glglgl

Reputation: 91139

Now perform an operation on the second handle, y:

>>> y+=1

This should be invoking PyNumber_InPlaceAdd on the PyObject that y points to.

Up to here you are right.

But in-place adding of numbers returns a distinct object, not the old one.

The old one, as it is immutable, keeps its value.

Upvotes: 0

Related Questions