3
print(id([]) == id([]))
# prints 'True'

print(id(list()) == id(list()))
# prints 'False'

x = []
y = []
print(id(x) == id(y))
# prints 'False'

Why does list() behave differently from [], in regards to the above code?

Puneet Singh
  • 344
  • 3
  • 12
  • One is literal and another is a function but are used interchangeably as syntactic sugar. https://stackoverflow.com/questions/33716401/whats-the-difference-between-list-and – Rahul Apr 09 '19 at 06:14
  • 1
    [The code you've added in your edit doesn't do what you say it does.](https://ideone.com/yVv5zg) Both `[[]] * 5` and `[list()] * 5` create a single list containing 5 identical references to a single list, rather than 5 references to different lists. You probably did something like `x[0][0] = 'A'` instead of `x[0] = 'A'` in your actual test. – user2357112 Apr 09 '19 at 06:31
  • 3
    Reopening because the duplicate links don't say anything about the `id` behavior that is the focus of this question. – user2357112 Apr 09 '19 at 06:36
  • Yeah. I didn't check that earlier. So you're saying all the lists in `[[]] * 5` or `[list()] * 5` refer to the same list? @user2357112 – Puneet Singh Apr 09 '19 at 06:37
  • 3
    Note that having an object pool for literals is implementation defined. PyPy does not do this trick. – MisterMiyagi Apr 09 '19 at 06:50

2 Answers2

5

In your third id comparison, you are comparing the ID values of two objects with overlapping lifetimes. This must return False, by the contract of the id function. You would see the same behavior with list().

In both of your first two id comparisons, the objects involved have nonoverlapping lifetimes. Whether the ID values of objects with nonoverlapping lifetimes happen to be the same is an implementation detail, and you should not rely on it being one way or the other. The behavior is subject to change without notice.

In current CPython, the ID values happen to be the same with [] because [] uses the BUILD_LIST opcode, which invokes the C function PyList_New, and PyList_New uses a free list of deallocated list header structs to speed up allocation:

PyObject *
PyList_New(Py_ssize_t size)
{
    ...
    if (numfree) {
        numfree--;
        op = free_list[numfree];
        _Py_NewReference((PyObject *)op);

When lists are deallocated, the buffer holding the element pointers gets freed, but (up to a maximum free list size) the header holding information about stuff like object type, refcount, capacity, etc. goes on the free list:

static void
list_dealloc(PyListObject *op)
{
    ...
    if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))
        free_list[numfree++] = op;
    else
        Py_TYPE(op)->tp_free((PyObject *)op);
    Py_TRASHCAN_SAFE_END(op)
}

The list created by the first [] dies before the second [] expression, so its header goes on the free list, and then it gets reused by the second []. The id value is based on this header's address, so both lists have the same ID value.

In contrast, list() goes through tp_new and tp_init, and list's tp_new is PyType_GenericNew, which doesn't go through the same free list. PyType_GenericNew happens to allocate the two lists in different memory, producing different ID values.

user2357112
  • 260,549
  • 28
  • 431
  • 505
4

id(object) returns the id of the object.

An == expression evaluates to True if the objects referred to by the variables are equal (have the same contents).

So when you use the list() constructor each time a new object is created, and it's id is different, hence it evaluates to false, even though a new object is immediately discarded, it is created at first, thus the different id.

To the contrary, the [] is literal (a faster way to create an object) and always has the same ID, but as it creates a new object, the new object also gets it's new ID.

TLDR; [] is a literal and thus has a fixed ID, list() creates a new object, x=[] y=[] creates a new object x and y, hence the id of x and y are not the same, list() creates a new object each time, thus different id on each call.

Also x = [] will be faster than x = list(), but that is just a foot note and I can't figure out how to make it small, and put it like a foot note :)

anand_v.singh
  • 2,768
  • 1
  • 16
  • 35
  • 3
    `[]` and `list()` both create new objects. `[]` does not copy an existing list to an empty object. This answer is wrong. – user2357112 Apr 09 '19 at 06:26
  • 3
    You can verify this by using a [disassembler](https://ideone.com/fPjFza) to view the bytecode executed for `[]`, then going into the [bytecode evaluation loop source code](https://github.com/python/cpython/blob/v3.7.3/Python/ceval.c#L2270) to see the implementation of the `BUILD_LIST` opcode used. – user2357112 Apr 09 '19 at 06:28
  • Hmm I am reading on it, I will correct it if I am wrong, which look likely as literals are a fast way of creating objects, I think I have something wrong. – anand_v.singh Apr 09 '19 at 06:30