Are two variables referring to the same object? Python’s built-in `id(obj)` returns an integer identifying `obj` throughout its lifetime. In CPython, that integer is the object’s memory address. Its numeric value can differ between runs or machines, and other Python implementations may assign identities differently. section 6.1.3
A mutable default argument is a practical reason to check identity. A default list is created when the function is defined, then reused by calls that omit the argument. Printing `id(tasks)` across those calls makes the sharing visible. If each call needs a fresh list, default the parameter to `None` and create the list inside the function. section 6.1.3
Listing 6.2 Using the same mutable object defined in the function
def append_task(task, tasks=[]):
tasks.append(task)
print(f"Tasks: {tasks}; id: {id(tasks)}") #1
append_task.__defaults__ #2
# output: ([],)
id(append_task.__defaults__[0])
# output: 4356663616
append_task("Homework")
# output: Tasks: ['Homework']; id: 4356663616
append_task("Laundry")
# output: Tasks: ['Homework', 'Laundry']; id: 4356663616
append_task.__defaults__
# output: (['Homework', 'Laundry'],)
The matching printed IDs show that both calls append to the list stored in the function’s defaults. The particular integer is not important. section 6.1.3
Check a shallow copy
After `task_copied = copy(task)`, a change to `task_copied.tags` also appears in `task.tags`. The checks `task.tags is task_copied.tags` and `id(task.tags) == id(task_copied.tags)` are both true: the two tasks share one interior list. Prefer `is` when testing object identity directly; `==` is appropriate between the integer values returned by separate `id` calls. section 10.3.2
The same check can confirm that `self` inside `__init__` refers to the instance assigned to a variable: `id(self)` and `id(task)` match. For a dictionary view, its contents can change when the dictionary changes while the view object keeps the same identity. section 8.1.1 section 3.4.7
