First of all, [] is not an array, it's a list. The issue here is how the attribute resolution and mutable variables work. Let's start with
class Foo(object):
a = {}
b = 0
c = []
This creates a class with three attributes — those are available either through class itself (Foo.a, for example), or through class' instance (Foo().a). Attributes are stored in a special thingy called __dict__. Both class and an instance have one (there are cases in which this is not true, but they're irrelevant here) — but in the case of Foo, the instance __dict__ is empty when the instance is created — so when you do Foo().a, in reality you're accessing the same object as in Foo.a.
Now, you're adding __init__.
class Foo(object):
# ...
def __init__(self):
self.x = {}
This creates an attribute not in the class' __dict__, but in the instance one, so you cannot access Foo.x, only Foo().x. This also means x is a whole different object in every instance, whereas class attributes are shared by all of the class instances.
Now you're adding your mutation method.
class Foo(object):
# ...
def mutate(self, key, value):
self.x[key] = value
self.a[key] = value
self.b = value
self.c.append(value)
Do you recall that self.x = {} creates an instance attribute? Here self.b = value does the same exact thing — it doesn't touch the class attribute at all, it creates a new one that for instances overshadows the shared one (that's how references work in Python — assignment binds the name to an object, and never modifies the object that the name was pointing to).
But you don't rebind self.a and self.c — you mutate them in-place (because they're mutable and you can do that) — so in fact you're modifying the original class attributes, that's why you can observe the change in the other instance (as those are shared by them). self.x behave differently, because it's not a class attribute, but rather an instance one.
You also print only first element of self.c — if you'd print all of it, you'd see it's [10, 20].