What everyone else says is correct, but I want to add my way of thinking that may be helpful if you have experience with a language like C or C++.
Every variable in Python is a pointer (well, the technical term is a "reference", but I find that more difficult to visualize than "pointer"). You know how in C/C++ you can get a function to output multiple values by passing in pointers? Your code is doing essentially the same thing.
Of course, you may be wondering, if that is the case, why don't you see the same thing happening to ints, strs or whatnot? The reason is that those things are immutable, which means you cannot directly change the value of an int or a str at all. When you "change an integer", like i = 1, you are really changing the variable, pointing it to a different int object. Similarly, s += 'abc' creates a new str object with the value s + 'abc', then assigns it to s. (This is why s += 'abc' can be inefficient when s is long, compared to appending to a list!)
Notice that when you do a = [0], you are changing a in the second way --- changing the pointer instead of the object pointed to. This is why this line doesn't modify x.
Finally, as the others has said, w = z[:] makes a copy. This might be a little confusing, because for some other objects (like numpy arrays), this syntax makes a view instead of a copy, which means that it acts like the same object when it comes to changing elements. Remember that [] is just an operator, and every type of object can choose to give it a different semantical meaning. Just like % is mod for ints, and formatting for strs --- you sometimes just need to get familiar with the peculiarities of different types.
w = z[:]makes a copy.a = [0]re-assigns the variable, so it's not changing the content of x.c[0] = 2changes an item of the array, which is stillz, so it changes the content ofz