Variables in python are just names for object, and assigning to them just changes the object designated by that name.
However, when assigning to elements of a list, you are changing the object referenced by that position in the list.
Thus:
pointer = self.storage[index] # 1
pointer = pointer.next # 2
self.storage[index] = self.storage[index].next # 3
(1) Makes pointer a name for the object referenced at storage[index]
(2) On the right side of the assignment =, you look for the name attribute of the object referenced by pointer. This is self.storage[index].name. The assignment will make the pointer variable refer to self.storage[index].name. To update the self.storage list, you need to operate on the list object itself. As opposed, self.storage[index], as referenced by pointer, is just "some object". You don't even have a way to get back to the list from there, so there's no way to change the list here.
(3) Here, however, you are changing the list self.storage, replacing the element at index. You could have doneself.storage[index] = pointer` at this point, with the same effect.
Of course python works with references (or pointers) under the hood. my_list[1] = obj doesn't allocate space for obj, it will store a reference to it. But local and global variables are not references, they are names.
In the end, global and local namespaces are just mapping variable names to objects. They are usually just normal dictionaries. You can see these by calling globals() and locals(). Or you can "implement" variable assignment just by changing that dictionary:
foo = 1
globals()["foo"] = 2
print(foo) # --> 2
Python assignment to variables is different than assignment to mutable objects like lists or dictionaries. Let's consider the following statements:
a = 1 # 1
b = [1] # 2
b[0] = 2 # 3
The first two assignments are doing the same. They are creating (or updating) global variables a and b, and map them to the int object 1 and to the list object [1], respectively. You'll have:
globals()
{
...
"a": 1,
"b": [1]
...
}
The third assignment is completely different. The assignment of this type is actually implemented by the object on the left. From the docs:
When a target is part of a mutable object (an attribute reference, subscription or slicing), the mutable object must ultimately perform the assignment and decide about its validity, and may raise an exception if the assignment is unacceptable.
This means that the third statement is implemented by the list object itself. Now the list in python keeps references to objects (it does not copy the objects). b[0], when used in an expression, is implemented by a special method of the list object and will return the object referenced by the list at position 0. b[0] = 2 is actually calling a special method of the list object, with the index (0) and the target object (2) as arguments, and the list implementation decides how it updates itself: it will change the reference at index 0 to the new object.
This difference is embedded in python's assignment statement. I've liked you the full reference, too.
More details:
pointer = self.storage[index]andpointer = pointer.nextsimply assigns two different objects to the same variable in succession, but that doesn't affect those objects at all.