Why Does Python’s ‘is’ Operator Give Different Results for Identical Integers?
This article explains why the same integer values can produce different results with the ‘is’ operator in Python, covering small‑integer caching, the general integer object pool, constant pools, and the distinction between whole‑module and line‑by‑line execution modes.
Interview Question
Given the following code, predict its output when run in an interactive terminal (e.g., IPython) versus when executed as a script.
a = 256
b = 256
c = 257
d = 257
def foo():
e = 257
f = 257
print('a is b: %s' % (a is b))
print('c is d: %s' % (c is d))
print('c is e: %s' % (c is e))
print('e is f: %s' % (e is f))
foo()Results
IPython execution
In [31]: a = 256
In [32]: b = 256
In [33]: c = 257
In [34]: d = 257
In [35]: def foo():
...: e = 257
...: f = 257
...: print('a is b: %s' % (a is b))
...: print('c is d: %s' % (c is d))
...: print('c is e: %s' % (c is e))
...: print('e is f: %s' % (e is f))
...:
In [36]: foo()
a is b: True
c is d: False
c is e: False
e is f: TrueScript execution
$ python foo.py
a is b: True
c is d: True
c is e: False
e is f: TrueThe differing outputs stem from two lesser‑known Python implementation details.
Small Integers vs. Large Integers
Python optimizes integer handling to avoid excessive malloc/free calls. Integers are divided into “small” (range [-5, 256]) and “large” categories. Small integers are cached in a global small‑ints list that lives for the interpreter’s lifetime, so they are never garbage‑collected and require only one allocation.
Small‑Integer Object Cache
All small integers are pre‑allocated and stored in a linked list; any request for a small integer returns the existing object, eliminating repeated memory allocation.
General Integer Object Pool
Large integers are allocated from blocks ( PyIntBlock) that hold multiple PyIntObject instances. When a block is exhausted, a new one is malloc‑ed. Freed large‑integer objects are returned to a free list for reuse, reducing malloc/free overhead.
struct _intblock {
struct _intblock *next;
PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;
static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;Each block can store about 82 integer objects. block_list tracks all allocated memory, while free_list tracks available slots. When free_list is empty, Python allocates a new block; when a large‑integer’s reference count drops to zero, its memory is returned to free_list instead of the system.
Parsing Modes: Whole‑Module vs. Line‑by‑Line
Whole‑Module Execution
Running the script treats the entire file as a single compilation unit (a function object). All constants in that unit share one constant pool, so variables c, d, e, and f reference the same large‑integer objects, making c is d and e is f evaluate to True.
Line‑by‑Line Execution
In an interactive interpreter each line (or complete input block) is compiled separately, producing distinct function objects with separate constant pools. Consequently, c and d come from different pools, so c is d is False, while e and f are still compiled together, keeping
e is f True.
Conclusion
Understanding Python’s integer caching and constant‑pool mechanisms reveals how memory‑pool designs can improve performance, and it also serves as a subtle interview question to differentiate developers who deeply understand language internals.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
