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

Chris Barker chrishbarker at home.net
Fri Jun 8 02:03:46 CEST 2001


Alain TESIO wrote:
> 
> Hi, sometimes class attributes added just after
> "class ...:" outside a functions interfere among
> instances, more precisely mutable objects are
> linked to the same object as if it were a static
> attribute.
> 
> See the test case below.
> 
> If I delete the line "val=[None]" in class
> Y and add "self.val=[None]" in its __init__
> it works as class X.
> 
> Is it a bug or did I miss something ?

Nope, that's exactly as it is supposed to work. All the variables in a
class definition are members of the definition itself. You want this,
because you wouldn't want to make a copy of all the methods and all
defined in the class. Remember that all variable in Python are just a
reference to something. There is only one copy of all the stuff in a
class definition.

When you create an instance a reference to that instance is passed in as
the first parameter to __init__. by convention, it is called self.
Anything to want to be unique to that instance need to be put in self.
When self is passed in, it comes with references to all the class
variables, including all the methods defined, adn in your case, "val".

Now, the reason you have your confusion is that since everything in
Python is a reference, the distiction that matters is whether it is
mutable or not. A mutable object (a list) can be changed in place. An
immutable object can not. If you assign a new value to a name that
references an object, the name then gets assigned to your new object,
and the old one is de-refereced (it will be garbage collected if there
is nothing else pointing to it. Whe you alter a mutable object, however
(list[index] = something) you have not changed the reference, but you
have changed the object it is pointing to.

So in your example:
> class X:
> 
>         val=None
# val no is a attribute of "X"
> 
>         def __init__(self):
# self is an instance of class "X", so it has a reference to val
>                 for i in range(3):
>                         self.val=random.random()

# you have now changed what self.val refernces, a new random number.
X.val is still referencing "None" for each instance, "self" is a new
copy, so self.val is a new variable.

> 
>         def __repr__(self):
>                 return str(self.val)
> 
> class Y:
> 
>         val=[None]
> 
>         def __init__(self):
# same as above so far.
>                 self.val[0]=random.random()
# self.val still references the list that X.val references, but you have
now changed the contents of that list. For each instance, you get
another reference to the SAME list, so each time it is changed, it looks
different to every variable that references it.

> 
>         def __repr__(self):
>                 return str(self.val)
> ============ end of file 'cl.py'

Try this:

>>> import cl
>>> print cl.X.val
None
>>> x1=cl.X()
>>> print cl.X.val # note: it hasn't changed.
None

>>> print cl.Y.val
[0.54460536751587463]
>>> y1=cl.Y()
>>> print cl.Y.val # note it has changed
[0.092879566011803139]
>>>

This can be a handy feature if you want a set of data to be available to
all the instances of a class, and you want to be able to change that
dataset, making all the changes available to all instances. If you want
each instance to have it's own version of the data, put it in "self".

Try this:

>>> reload(cl) # it needs to be reloaded to get a clean copy of Y
<module 'cl' from 'cl.pyc'>
>>>
>>> print cl.X.val
None
>>> x1=cl.X()
>>> print cl.X.val # note: it hasn't changed.
None

>>> print cl.Y.val
[None]
>>> y1=cl.Y()
>>> print cl.Y.val # note it has changed
[0.91001740255497787]
>>>

Note that this is just a more convoluted of the simple demo of mutable
objects.

>>> a = [1,2,3,4]
>>> b = a
>>> # A and b reference the same list

>>> a[:] = [5,6,7,8]
>>> # a[:] assigns to the CONTENTS of the list

>>> a
[5, 6, 7, 8]
>>> b
[5, 6, 7, 8]
>>> # since there is only one list, a and b are both changed

>>> a = (1,2,3,4)
>>> b = a
>>> # a and b point to the same tuple

>>> a[:] = (5,6,7,8)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support slice assignment
>>> # oops! tuples are imutable, you can't change their contents
>>> a = (4,5,6,7)
>>> # a now points to a new tuple, b still points to the old one
>>> a
(4, 5, 6, 7)
>>> b
(1, 2, 3, 4)
>>> # there are now two tuples, a references one and b refernces the other



-- 
Christopher Barker,
Ph.D.                                                           
ChrisHBarker at home.net                 ---           ---           ---
http://members.home.net/barkerlohmann ---@@       -----@@       -----@@
                                   ------@@@     ------@@@     ------@@@
Oil Spill Modeling                ------   @    ------   @   ------   @
Water Resources Engineering       -------      ---------     --------    
Coastal and Fluvial Hydrodynamics --------------------------------------
------------------------------------------------------------------------



More information about the Python-list mailing list