Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class attribute shadowing in Python class [duplicate]

Tags:

python

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([])
like image 771
jha-G Avatar asked Jun 27 '16 06:06

jha-G


1 Answers

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

like image 65
jha-G Avatar answered Sep 30 '22 08:09

jha-G