Mutable class attributes are shared among all instances, is it normal ?

Carel Fellinger cfelling at iae.nl
Fri Jun 8 21:44:17 EDT 2001


Alain TESIO <alain at onesite.org> wrote:
> Hi, sometimes class attributes added just after
> "class ...:" outside a functions interfere among
> instances, more precisely mutable objects are

That's why they are called *class* attributes:)

...
> Is it a bug or did I miss something ?

Others already replied, but I think they too missed that you were using
class attributes as if they were instance attributes.  Let me explain.
    
    >>> class SomeClass:
    ...     someClassAttribute = "spam"
    ...     def someMethod(self):
    ...         print self.someClassAttribute
    
here we have a class and a class attribute.  This class attribute exists
in the namespace of SomeClass as can be seen with the dir function:

    >>> dir(SomeClass)
    ['__doc__', '__module__', 'someClassAttribute', 'someMethod']
    
and can be accessed as follows:

    >>> print SomeClass.someClassAttribute
    spam
    >>> SomeClass.someClassAttribute = "spam and spam"
    >>> print SomeClass.someClassAttribute
    spam and spam
    
When we create an instance of this class, the use of dir will learn us
that someClassAttribute is not automagically transformed into an
instance attribute:

    >>> someInstance = SomeClass()
    >>> dir(someInstance)
    []

yet, like the methods defined in the class, it can be accessed as if
it where an attribute of the instance, like:

    >>> print someInstance.someClassAttribute
    spam and spam
    >>> someInstance.someMethod()
    spam and spam

So far all is fine, you change a class attribute through the class
namespace and you can use it through the instance namespace as well.

BUT when you try to change the class attribute through the instance
namespace you're in for a small surprise.  Assignment in Python is
actually (re)binding a name in a name space to an object.  And in this
case a new name with binding is created in the instance namespace:

    >>> someInstance.someClassAttribute = "no more spam"
    >>> dir(someInstance)
    ['someClassAttribute']
    >>> someInstance.someMethod()
    no more spam

Mind you that the name of this newly bound name is highly misleading
as it actually is an instance attribute now, that hides the true class
attribute that still exists, as the following shows:

    >>> print SomeClass.someClassAttribute
    spam and spam
    >>> anOtherInstance = SomeClass()
    >>> dir(anOtherInstance)
    []
    >>> anOtherInstance.someMethod()
    spam and spam

To top this off we now should consider the behaviour of mutable versus
non mutable objects.  This has been dealt with in other replies, so
let it suffice to say that changing the state of a mutable object
doesn't involve any name rebinding, so all names keep refering to the
same (although internally changed) object.


> ============ start of file 'cl.py'
> import random

> class X:
> 	val=None

        ^^^ here val is a class attribute

> 	def __init__(self):
> 		for i in range(3):
> 			self.val=random.random()

                        ^^^^^^^^ self.val is now an instance attribute

> class Y:
> 	val=[None]

> 	def __init__(self):
> 		self.val[0]=random.random()

                ^^^^^^^^^^^ val is not rebound, (though val[0] is)
                            so it remains a class attribute


To summarize

class attributes can be inspected and changed within the class realm with:

   someClass.someClassAttribute

class attributes can be inspected through instances with:

   anyInstance.someClassAttribute

but assignment to it in an instance will instead create an instance
attribute and hide the still existing true class attribute.

So if you really need class attributes, that's fine, but pay attention
that you only try to rebind them within the class and not within instances
of that class and look out for "someInstance.someClassAttribute = ..."

If on the other hand, you need instance attributes and want to use a
class attribute to provide a default initial value for that instance
attribute, that's fine too, but pay attention that you really rebind
them within the instance name space turning them into true instance
attributes upon change and look out for the sticky behaviour of
mutable objects.

-- 
groetjes, carel



More information about the Python-list mailing list