5

I was learning this and this to understand class attributes. But got confused with the output of following code snippet.

class A:
    aliases = None
    name = None

    def __init__(self,name):
        self.name = name
        self.aliases = set([name])

    def add_aliases(self,a):
        self.aliases.add(a)

    def __repr__(self):
        return str(self.name) + str(self.aliases)

arr = []
for i in range(3):
    arr.append(A(i))
    arr[-1].add_aliases(i+1)

for item in arr:
    print item

A.aliases = set([]) ##Modify the static element of class
for item in arr:
    print item  

Python Interpreter: 2.7.9

And output is

0set([0, 1])
1set([1, 2])
2set([2, 3])
0set([0, 1])
1set([1, 2])
2set([2, 3])

And I was expecting something like this as an output.

0set([2, 3])
1set([2, 3])
2set([2, 3])
0set([])
1set([])
2set([])
Community
  • 1
  • 1
g-217
  • 2,069
  • 18
  • 33
  • This can't be a duplicate as this question is trying to suggest that `always refer class attributes by class name to make your code easier to comprehend.` – g-217 Jun 27 '16 at 09:19
  • you might wanna have a look at the answer i gave on the original post. – Shasha99 Oct 20 '16 at 14:30

1 Answers1

6

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

g-217
  • 2,069
  • 18
  • 33
  • *"We, do not notice if we are using immutable data structure"* - really? I think most people would notice if their other classes weren't sharing the attribute they expected them to. – jonrsharpe Jun 27 '16 at 06:36
  • @jonrsharpe Well the linked question says so, I just added this question to supplement the two questions mentioned. – g-217 Jun 27 '16 at 06:38
  • Then I think you've misunderstood the point the other questions were making about mutable vs. immutable class attributes. That's more to do with them sharing state when that *wasn't* expected, not not sharing state when it was. – jonrsharpe Jun 27 '16 at 06:39
  • Thanks for the clarification, I really did not notice this for very long. – g-217 Jun 27 '16 at 06:43