id returns a Python int object (the memory address of the object you're id-ing, though that's an implementation detail). But aside from very small ints (again, implementation detail), there is no int caching in Python; if you compute the same int two ways, it's two different int objects that happen to have the same value. Similarly, a fresh int is created on each call to id, even if the objects are the same.
The equivalence for id and is is that a is b implies id(a) == id(b), not id(a) is id(b) (and in fact, since ids are large numbers, id(a) is id(b) is almost always False).
Also note, your test case is flawed in other ways:
a = 3
b = 3
a is b
only returns True for the is comparison because of the small int cache in CPython; if you'd done:
a = 1000
b = 1000
a is b
a is b would be False; your assumptions about identity only hold in CPython for numbers in the range -5 to 256 inclusive, which are singletons for performance reasons, but all other ints are recreated as needed, not singletons.