I'll define a = [[1,1],[2,2],[3,3]] a bit differently. Let's say:
b = [1,1]
c = [2,2]
d = [3,3]
a = [b,c,d]
This is the exact same thing as you have, we just gave the inner list names so that it is easier to follow later on.
One might call those names references, or pointers to memory locations. If you're not familiar with those terms, a way of looking at is "b does not contain [1,1], it just knows where it is in the program's memory".
 
Ok, now at self.a[0] = self.a[1].
What this line does is it says to the program: "assign the first element of list a to be the same as the second one".
It does not say "copy the second element to the first one".
The difference is that both self.a[0] and self.a[1] point to the same array [2,2]. In other words a = [c,c,d] now.
self.a[1] = self.a[2] does the same thing, but for different elements, so a = [c, d, d].
So far, everything is as you expect it.
The array equals [[2,2],[3,3],[3,3]] when you print it, but the problem is that the [3,3] here is the same array, it can just be accessed by two elements of self.a.
Now let's tackle the self.a[2][0] += 1 part step by step:
- by calling self.a[2]we access the pointer to the arrayd = [3,3]
- following that logic, saying self.a[2][0]is the same as sayingd[0]
- since we have incremented d[0], and bothself.a[1]andself[2]point tod, bothself.a[1][0]andself.a[2][0]have changed.
Another way of looking at it is that your code is equivalent to:
class A():
    def __init__(self):
        self.b = [1,1]
        self.c = [2,2]
        self.d = [3,3]
        self.a = [self.b, self.c, self.d]
        self.a[0] = self.c
        self.a[1] = self.d
        self.d[0] += 1
        print(self.a)
aaa = A()
The array in self.a is not an array that contains arrays, it's just an array that contains variables that know where those arrays are (i.e. pointers or references).