You used id()
wrong. id([])
takes the memory id of an object that is discarded immediately. After all, nothing is referencing it anymore once id()
is done with it. So the next time you use id([])
Python sees an opportunity to re-use the memory and lo and behold, those addresses are indeed the same.
However, this is an implementation detail, one you can't rely on, and it won't always be able to reuse the memory address.
Note that id()
values are only unique for the lifetime of the object, see the documentation:
This is an integer which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id()
value.
(Bold emphasis mine).
That id(list())
can't re-use the memory location is probably due to the extra heap mutations caused by pushing the current frame on the stack to call a function, then popping it again when the list()
call returns.
Both []
and list()
produce a new empty list object; but you need to first create references to those separate lists (here a
and b
):
>>> a, b = [], []
>>> a is b
False
>>> id(a) == id(b)
False
>>> a, b = list(), list()
>>> a is b
False
>>> id(a) == id(b)
False
The same happens when you used [].__repr__
. The Python interactive interpreter has a special global name, _
, that you can use to reference the last result produced:
>>> [].__repr__
<method-wrapper '__repr__' of list object at 0x10e011608>
>>> _
<method-wrapper '__repr__' of list object at 0x10e011608>
That creates an extra reference, so the __repr__
method, and by extension, the empty list you created for it, are still considered active. The memory location is not freed and not available for the next list you create.
But executing [].__repr__
again, Python now binds _
to that new method object. Suddenly the previous __repr__
method is no longer referenced by anything and can be freed, and so is the list object.
The third time you execute [].__repr__
the first memory location is available again for reuse, so Python does just that:
>>> [].__repr__ # create a new method
<method-wrapper '__repr__' of list object at 0x10e00cb08>
>>> _ # now _ points to the new method
<method-wrapper '__repr__' of list object at 0x10e00cb08>
>>> [].__repr__ # so the old address can be reused
<method-wrapper '__repr__' of list object at 0x10e011608>
You never create more than two lists; the previous one (still referenced by _
) and the current one. If you wanted to see more memory locations, use variables to add another reference.