always the same object (2)

David Bolen db3l at fitlinxx.com
Tue Jan 20 20:26:26 EST 2004


Uwe Mayer <merkosh at hadiko.de> writes:

(...)
> I use struct.unpack() to unpack data from a binary file and pass the
> returned tuple as parameter to __init__ of a class that's supposed to
> handle the data:
> 
> class DataWrapper():
>         data = { }
>         def __init__(self, arg): #arg will contain a tuple
>                 data['var1'], data['var2'] = arg

Is this actual code?  You should get a SyntaxError on your use of ()
in the class line, and a NameError on your use of data[] since there
is no local data name within the __init__ function.  I'm assuming your
real code actually does self.data, and skips the () on the class
statement, but in the future, it's really best if you can post actual
operating code when discussing a problem.

> result = [ ]
> while not <end-of-file f>:
>         data = struc.unpack("4s4s", f.read(8)) 
>         record = DataWrapper( data ) # pass tuple from unpack 
>         result.append( record )
> 
> Then "result" contains a list with different objects, but the strings 
> in data['var1'] and data['var2'] are all the same.
> 
> Any ideas how to avoid this?

Yes, don't make data a class-level object.  By doing this you have a
single instance of the dictionary that data is pointing to, which is
shared among all of your DataWrapper instances.  Since that class
object is mutable, all of your changes in each instances __init__
affect that single object.  Instead, move the creation of the instance
name (and object) to the __init__ in your instance.

This can be a subtle point sometimes, because you won't run into this
problem with class level immutable objects (such as ints), because any
assignment to the same name in an instance becomes a rebinding
operation, thus making the name an instance variable at that point in
time.  This can be very convenient for implementing class level
defaults that take up no space in each instance, but doesn't work the
same as mutable types.

Note that if you really mean to do it, using a mutable type at class
level can be a very useful construct, since it permits sharing of
information among all instances of a class.  But you have to be
expecting it :-)

For example, the immutable case:

    >>> class Foo:
    ...     var = 10
    ...     def set_var(self, value):
    ...         print 'Var ID:', id(self.var)
    ...         self.var = value
    ...         print 'Var ID:', id(self.var)
    ... 
    >>> x = Foo()
    >>> print id(Foo.var), id(x.var)
    7649236 7649236 
    >>> x.set_var(5)
    Var ID: 7649236
    Var ID: 7649224
    >>> print id(Foo.var), id(x.var)
    7649236 7649224
    >>> print Foo.var, x.var
    10 5

Foo.var always references the same object, but once an assignment has
been made within the instance, it no longer points to the same object
as initialized at class level.  Thus, each instance references
independent objects with their own values.

Contrast this with the mutable case:

    >>> class Foo:
    ...     var = []
    ...     def set_var(self, value):
    ...         print 'Var ID:', id(self.var)
    ...         self.var.append(value)
    ...         print 'Var ID:', id(self.var)
    ... 
    >>> x = Foo()
    >>> y = Foo()
    >>> print id(Foo.var), id(x.var), id(y.var)
    8042256 8042256 8042256
    >>> print Foo.var, x.var, y.var
    [] [] []
    >>> x.set_var(10)
    Var ID: 8042256
    Var ID: 8042256
    >>> y.set_var(5)
    Var ID: 8042256
    Var ID: 8042256
    >>> print id(Foo.var), id(x.var), id(y.var)
    8042256 8042256 8042256
    >>> print Foo.var, x.var, y.var
    [10, 5] [10, 5] [10, 5]

I made the class object a list and appended values to it just to
highlight how calls to multiple instances were affecting the same
object, but the principle is the same with a dictionary or any other
mutable object.  Since the changes in set_var are made by mutating the
existing object (versus rebinding the same name to a new object), all
the instances share the single Foo.var list.

So you could use this construct if you really wanted an object that
was seen by all instances and updated by any instance.  But if you
just wanted an empty list (or dictionary) independently in each
instance, you'd want to do something like:

    >>> class Foo:
    ...     def __init__(self):
    ...         self.var = []
    ...     def set_var(self, value):
    ...         print 'Var ID:', id(self.var)
    ...         self.var.append(value)
    ...         print 'Var ID:', id(self.var)
    ... 
    >>> x = Foo()
    >>> y = Foo()
    >>> print id(Foo.var)
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    AttributeError: class Foo has no attribute 'var'
    >>> print id(x.var), id(y.var)
    7756800 8053200
    >>> x.set_var(10)
    Var ID: 7756800
    Var ID: 7756800
    >>> y.set_var(5)
    Var ID: 8053200
    Var ID: 8053200
    >>> print id(x.var), id(y.var)
    7756800 8053200
    >>> print x.var, y.var
    [10] [5]

-- David



More information about the Python-list mailing list