Good old name and value confusion, here with a dash of complicated code. Let's work backwards.
First, you're comparing using ==. Here those are both objects of type RedBlackTreeNode, which doesn't override the equality behaviour. So it's actually an identity test.
Secondly we trace back where the two operands c.left and b come from. We find that c.left was assigned the value of a before a was changed to refer to b. Therefore, c.left refers to the node originally bound as a but no longer known under that name.
In C-style pointer language, every variable is actually a pointer (reference), and pointers are always copied by value; there is no type for pointer to pointer, so they can't alter each other even though they can point to the same data.
Python's objects are entities in their own right, and variables are merely names referring to objects. Therefore reassigning a only meant there was one less way to find the object it previously referred to. Each name (whether in your locals, the module's globals or an object like c) only refers to some object, they know nothing of each other (though there may be reference counts to track their existence).
Ned Batchelder's article on Python Names and Values might clear this up a bit.
It's also possible for things to look like these names but redirect to more complex behaviour. That's what custom attribute access and properties do, particularly for things that translate like ctypes structures.