And the explanation is that when we write self.aliases = set([]) we are actually creating a new instance attribute, shadowing the class attribute.
So, if we make our __init__ function as follows we get the expected output.
def __init__(self,name):
self.name = name
A.aliases = set([name]) #using the class attribute directly
Also consider following code snippet:
class A:
aliases = set([])
name = None
def __init__(self,name):
self.name = name
self.aliases.add(name) # using the class attribute indirectly.
def add_aliases(self,a):
self.aliases.add(a)
def __repr__(self):
return str(self.name) + str(self.aliases)
Since in this case, we're not creating an instance attribute, there is no shadowing. And the test code in the question, would produce this output:
0set([0, 1, 2, 3])
1set([0, 1, 2, 3])
2set([0, 1, 2, 3])
0set([])
1set([])
2set([])
which is expected, as the class attributes are shared across all instances.
Here A.alias can also be referred as self.alias inside init or any other function. And since it did not shadow the static attribute, gave the expected output -- the case when all the the object share a common attribute.
A person not aware of this concept will not notice anything while using immutable data structure like string etc. But in case of data-structures like list and dictionary this may surprise.
Also consider following definition of init.
def __init__(self,name):
self.name = name
self.aliases.add(name) # referring to class attribute
self.aliases = set([]) # creating a instance attribute
And this case also, it ended up creating instance attribute and output produced by test code is:
0set([1])
1set([2])
2set([3])
0set([1])
1set([2])
2set([3])
And form all this my learning is:
Always refer class attribute with class name and instance attribute with object name, i.e. write A.aliases when you mean class attribute aliases, do not write self.aliases to indirectly refer self.aliases