Inexplicable behavior in simple example of a set in a class

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Jul 3 04:25:29 CEST 2011


Saqib Ali wrote:

> I have written two EXTREMELY simple python classes. One class
> (myClass1) contains a data attribute (myNum) that contains an integer.
> The other class (myClass2) contains a data attribute (mySet) that
> contains a set.
> 
> I instantiate 2 instances of myClass1 (a & b). I then change the value
> of a.myNum. It works as expected.
> 
> Then I instantiate 2 instances of myClass2 (c & d). I then change the
> value of c.mySet. Bizarrely changing the value of c.mySet also affects
> the value of d.mySet which I haven't touched at all!?!?! 

But that is wrong -- you HAVE touched it. Look carefully: in myClass2, you
have this:

class myClass2:
    mySet = sets.Set(range(1,10))
    def clearSet(self):
        self.mySet.clear()

mySet is a class attribute, shared by ALL instances, and mySet.clear()
modifies it in place. This is exactly the same as this snippet:


>>> a = set([1, 2, 3])
>>> b = a
>>> b.clear()
>>> a
set([])
>>> b
set([])

In Python, attributes assigned in the class scope are shared between all
instances. Attributes assigned directly on self are not:

class Test:
    a = set([1, 2, 3])
    def __init__(self):
        self.b = set([1, 2, 3])


>>> x = Test()
>>> y = Test()
>>> x.a is y.a  # The same set is shared by both instances.
True
>>> x.b is y.b  # Each instance gets its own set.
False


So why does myClass1 behave differently? Simple: look at the clear method:

class myClass1:
    myNum = 9
    def clearNum(self):
        self.myNum = 0

It assigns a new attribute, rather than modifying the object in place!
Assignment to self.myNum creates a new unshared attribute:

>>> z = myClass1()
>>> z.myNum
9
>>> z.__dict__  # No instance attributes yet.
{}
>>> z.clearNum()
>>> z.__dict__  # Now there is one.
{'myNum': 0}
>>> z.__class__.myNum  # the class attribute still exists
9

The simplest way to fix this is to move the declaration of self.mySet into
the __init__ method.



-- 
Steven




More information about the Python-list mailing list