keepcalculator

06 Numbers and Powers

Python Integer Identity: Why It Varies by Run

Card No. 06·62189 · Updated · Standard formula, shown below

The calculator on this card is switched off while it is checked. The formula is below, and the worked example shows how to do the sum by hand.

Python integer identity differs across implementations and runs because the CPython interpreter caches only small integers (typically -5 to 256) and sometimes reuses objects for larger integers within the same run, while other implementations like PyPy or Jython have different object models and memory management. In short, the is operator on integers is not a reliable way to compare values; use == for value equality.

Formula

The calculator above is a simple demonstration: it takes two integers and shows whether they are equal by value (==) and whether they are the same object (is). The first output is a placeholder because the calculator cannot directly evaluate Python’s is; instead, it shows the value equality result. The actual identity check is done by the Python interpreter at runtime, not by a formula.

Worked Example

With the default inputs A = 256 and B = 256, the value equality a == b is True, so the output shows 1. In CPython, both variables reference the same cached integer object, so a is b is also True. The calculator displays the value equality result because it cannot execute Python’s identity operator.

Background: Why Integers Are Objects

In Python, everything is an object, including integers. When you write x = 1000, Python creates an integer object with the value 1000 and assigns a reference to it. The is operator checks whether two references point to the same object in memory, while == compares the values of the objects. For small integers, CPython pre-creates a cache of objects for values from -5 to 256. This cache is shared across the entire interpreter, so any reference to the integer 5 points to the same object. For larger integers, Python typically creates a new object each time, but there are exceptions due to constant folding and other optimizations.

How CPython’s Small Integer Cache Works

CPython, the reference implementation, maintains an array of integer objects for values from -5 to 256. When you assign a = 5 and b = 5, both variables point to the same cached object. This is why a is b returns True for small numbers. The cache is initialized at interpreter startup and lives for the entire process. The range -5 to 256 is not arbitrary; it covers common small numbers used in loops, indices, and counters. For values outside this range, Python allocates a new object each time you create an integer, unless the interpreter can reuse an object through optimizations like constant folding.

Why Identity Varies Across Runs

Even within CPython, the identity of integers outside the small cache can change between runs. Consider this code:

a = 1000
b = 1000
print(a is b)  # may be True or False

In a single interactive session, you might see True because the interpreter may reuse the same object for both assignments. But in a script, the result can be False because each assignment creates a new object. The behavior depends on the compiler’s constant folding: if the same integer literal appears in the same code block, CPython may reuse the same object. However, this is an implementation detail and not guaranteed. Across different runs of the same script, the memory layout and garbage collection can change, so the identity may differ. For example, running the same script twice might give different results for a is b when a and b are large integers.

Differences Across Python Implementations

Python is not just CPython. Other implementations include PyPy, Jython, IronPython, and MicroPython. Each has its own object model and memory management, leading to different integer identity behavior.

  • PyPy uses a JIT compiler and may optimize integers to unboxed machine words, meaning they are not objects at all in some contexts. This makes is on integers unpredictable and often returns False for values that would be cached in CPython.
  • Jython runs on the Java Virtual Machine and maps Python integers to Java Integer objects. Java has its own integer cache for values from -128 to 127, but the behavior may differ from CPython’s.
  • IronPython runs on .NET and uses .NET’s object model, which also has its own integer caching.
  • MicroPython is designed for microcontrollers and may not implement the small integer cache at all due to memory constraints.

Because of these differences, relying on is for integer comparison is non-portable and can lead to subtle bugs.

Common Misconceptions and Pitfalls

A frequent misconception is that is compares values. It does not; it compares identity. For integers, this can be confusing because small integers are cached, so is often works by accident. But consider this example:

a = 257
b = 257
print(a is b)  # False in CPython, but may be True in some contexts

This can break code that uses is to check for a specific integer value. Another pitfall is relying on the small integer cache for sentinel values. If you need a unique sentinel, use a custom object instead of an integer. Also, be aware that the cache range is not part of the Python language specification; it is a CPython implementation detail. Other implementations may have different ranges or no cache at all.

How to Compare Integers Correctly

The only safe way to compare integers is to use == for value equality. If you need to check whether two variables refer to the same object, use is only when you know the objects are interned, such as for None, True, False, and small integers in CPython. For everything else, avoid is with integers. If you are writing code that must work across implementations, never use is for integers. Instead, use == or compare with isinstance when checking types.

When Does Integer Identity Actually Matter?

In practice, integer identity rarely matters for correctness. It can matter for performance in rare cases, such as when you want to avoid creating duplicate objects in a memory-constrained environment. For example, if you have a large list of integers, using small integers that are cached can save memory because they are shared. However, for large integers, each unique value is a separate object, so memory usage can be high. In most applications, you should not worry about integer identity and simply use ==.

Frequently Asked Questions

Why does 256 is 256 return True but 257 is 257 returns False?

CPython caches integers from -5 to 256, so all references to 256 point to the same object. The value 257 is outside the cache, so each occurrence creates a new object, making is return False.

Is the small integer cache guaranteed in Python?

No. The cache is an implementation detail of CPython. The Python language specification does not require it. Other implementations like PyPy or Jython may behave differently.

Can I rely on is for integers in my code?

No, you should never rely on is for integer comparison. Use == for value equality. The behavior of is is unpredictable across implementations and even across runs.

Why does the same script sometimes give different results for is on large integers?

Because the interpreter may reuse or create new objects depending on memory allocation, garbage collection, and compiler optimizations. These factors can vary between runs, leading to different identity results.

Does Python’s small integer cache affect memory usage?

Yes, it can reduce memory usage for small integers because they are shared. However, for large integers, each value is a distinct object, so memory usage can be higher.

How can I force two variables to refer to the same integer object?

You generally cannot force this for arbitrary integers. For small integers within the cache, assigning the same value will point to the same object. For larger integers, you could use a custom class or a singleton pattern, but that is rarely needed.

Related

Formula used
in a | Integer A | - | 256 | -1000000 | 1000000
in b | Integer B | - | 256 | -1000000 | 1000000
out same | Same object? (is) | - | (a == b) * 1 + (a is b) * 0 | 0
out eq | Equal value? (==) | - | (a == b) * 1 | 0

Each line is one input (in, sel, list) or one result (out: label, unit, formula, decimals). How the calculators work

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →