Force copy of a small int in Python

Viewed 852

As a toy problem, I tried to come up with objects a, b such that

type(a) == type(b) == int # True
a + 1 == b + 1 == 1       # True
a is b                    # False

It seems that deepcopyfalls back on _deepcopy_atomic, as discussed here.

Is it possible to create a copy of a small int in Python?

4 Answers

The answer to your question depends on what you consider to be a small int. As per documentation:

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object.

In the case of integers, line a is b is equivalent to id(a) == id(b). For the range [-5, 256] ids for all of the numbers are predefined, which means that a and b act as aliases for the existing object.

The only way in which a, b would satisfy your condition a + 1 == b + 1 == 1 is when a = b = 0. Since 0 is in the aforementioned range, it is not possible to make a is b return False.

This likely isn't the intent of the question, but the 3 assertions in the question can be satisfied if int is allowed to be overridden, such that the following code will pass:

int = type('', (int,), dict(vars(int)))
a = int(0)
b = int(0)
assert type(a) == type(b) == int
assert a + 1 == b + 1 == 1
assert a is not b

Yes, it's possible, and without low level hacks or cheats:

>>> a = 0
>>> b = 9**99 % 9**99
>>> type(a) == type(b) == int
True
>>> a + 1 == b + 1 == 1
True
>>> a is b
False

I'm doing this locally with Python 3.8.1, but you can also reproduce it at repl.it and at www.python.org/shell/.

For small positives:

>>> a = 7
>>> b = (9**99 + a) % 9**99
>>> b, type(b), a == b, a is b
(7, <class 'int'>, True, False)

For small negatives:

>>> a = -2
>>> b = (9**99 + a) % -9**99
>>> b, type(b), a == b, a is b
(-2, <class 'int'>, True, False)

I was able to get the following to work for CPython:

import ctypes

x = 2
y = 3

ctypes.c_int.from_address(id(y) + 24).value = x

print(x == y) # True
print(x is y) # False

I couldn't get away with x = 0 or x = 1 without crashing the interpreter.

Related