Style question - defining immutable class data members

Gary Herron gherron at islandtraining.com
Sat Mar 14 19:30:00 EDT 2009


Maxim Khitrov wrote:
> On Sat, Mar 14, 2009 at 4:31 PM, Gary Herron <gherron at islandtraining.com> wrote:
>   
>>> Perhaps a different example would help explain what I'm trying to do:
>>>
>>> class Case1(object):
>>>        def __init__(self):
>>>                self.count = 0
>>>                self.list  = []
>>>
>>>        def inc(self):
>>>                self.count += 1
>>>                self.list.append(self.count)
>>>
>>>        def val(self):
>>>                return (self.count, self.list)
>>>
>>> class Case2(object):
>>>        count = 0
>>>        list  = []
>>>
>>>        def inc(self):
>>>                self.count += 1
>>>                self.list.append(self.count)
>>>
>>>        def val(self):
>>>                return (self.count, self.list)
>>>
>>> for i in xrange(10):
>>>        c1 = Case1()
>>>        c2 = Case2()
>>>
>>>        for j in xrange(i):
>>>                c1.inc()
>>>                c2.inc()
>>>
>>>        v1, l1 = c1.val()
>>>        v2, l2 = c2.val()
>>>
>>>        print v1 == v2, l1 == l2
>>>
>>> The only difference between Case1 and Case2 classes is where the count
>>> and list attributes are defined. You will notice that for an immutable
>>> type (count), this doesn't matter. On the last line, v1 == v2 is
>>> always True. When the type is mutable (list), you must define it in
>>> __init__. This isn't about class attributes or shared instance
>>> attributes/constants. This is about a small optimization in defining
>>> per-instance variables. This optimization only applies to immutable
>>> types.
>>>
>>> - Max
>>> --
>>> http://mail.python.org/mailman/listinfo/python-list
>>>
>>>       
>> But now you are not listening to what people are telling you.  It has
>> *nothing* to do with the mutability/immutability of the integer and the list
>> your two classes create.
>>
>> The difference is this:
>>
>>   For C1:  You create 10 instances of C1.  Each one creates its own  count,
>> and a list variables, and manipulates them calls to inc and val.  Then each
>> on is discarded as you go through the next pass on the outer loop.
>>     
>
> Correct, though the discarded part makes no difference.
>
>   
>>   For C2;  You create 10 instances of C2, but these 10 instances each
>> manipulate values created once in the class itself.  The values manipulated
>> by one instance of C2 in one pass through the loop are not affected when, on
>> the next pass through the loop, that instance is destroyed and another
>> instance is created.
>> So...
>>     
>
> Incorrect. Only the count is unaffected, which was the whole point of
> my question.
>   


No, you are still misinterpreting your results.  But you can be forgiven 
because this is quite a subtle point about Python's attribute access.

Here's how it works:

On access, self.count (or self.anything) attempts to find "count" in the 
instance variable "self".  If that fails, it attempts to lookup "count" 
in the class.  If that fails it attempts to lookup "count" in a global 
context, ...).    However, on assignment self.count=... *must* 
necessarily mean the local instance variable -- no defaulting to a class 
variable on assignment.

So ... 

With your class C2, the line
   self.count +=1
which really translates into
   self.count = self.count + 1
has two different actions depending on the pass through the inner loop.
The first time through (when no instance variable "count" exists), that 
line means
    <create and assign instance variable "count"> = <class variable 
count> + 1
and succeeding passes through (now that self.count is defined) become
    <instance count> = <instance count> +1
which is now the same as your class C1

To complete the subtlety, if you were to do
   self.list = self.list + [i]
you would see list behaving just as count.  However since,
    list.append(...)
is an in-place operation, and not an assignment, the creation of an 
instance variable
is not provoked, and so all instances continue using the single class 
variable.

There is a lesson to be learned here.

Class variables can't be the target of an assignment through self, 
because that
creates an instance variable.  You can, however, assign to a class 
variable through
the class:
  C2.count = ...



   


>>  If you want a variable that records/supplies some value across *all*
>> instances of a class, use a class variable.  (Or use a global variable -- it
>> would have the same effect.)
>>
>>  If you want a variable whose value is unique to each instance of a class,
>> then make it an instance variable.
>>
>> Gary Herron
>>     
>
> I never thought that such simple question would turn into this. David
> Stanek gave me the answer I was looking for (thank you). You, on the
> other hand, are still going after the wrong issue. Once again, here's
> the same example using Terry's suggestions. No class instances are
> being destroyed until the very end.
>
> class Case1(object):
>        def __init__(self):
>                self.count = 0
>                self.list  = []
>
>        def inc(self):
>                self.count += 1
>                self.list.append(self.count)
>
>        def val(self):
>                return (self.count, self.list)
>
> class Case2(object):
>        count = 0
>        list  = []
>
>        def inc(self):
>                self.count += 1
>                self.list.append(self.count)
>
>        def val(self):
>                return (self.count, self.list)
>
> c1a, c1b = Case1(), Case1()
> c2a, c2b = Case2(), Case2()
>
> c1a.inc(), c1b.inc()
> c2a.inc(), c2b.inc()
>
> print c1a.val(), c1b.val(), c2a.val(), c2b.val()
>
> And the output:
> (1, [1]), (1, [1]), (1, [1, 1]), (1, [1, 1])
>
> The first element of every tuple is the same. This is the count, which
> is immutable. The second element is not the same for c2[a,b]. This is
> the list, which is mutable. My question was about immutable values,
> and for those the who cases are identical. In the second case, c2a and
> c2b begin with count referring to the same '0' object. This is where
> the time and space savings are made. But because that object is
> immutable, when += 1 operation is performed, a copy is made for each
> instance. At that point, I am not sharing any values between class
> instances.
>
> The whole point is that this is a quick(er) way of providing initial
> values for class instance variables when those values are immutable.
> When/if that initial value is changed, a copy is made. In Case1, that
> copy is made from the very begging in __init__. Please try to
> understand what the question is about before responding to it.
>
> - Max
>   




More information about the Python-list mailing list