dictionary containing instances of classes behaving oddly

Discussion in 'Python' started by Ben, Dec 28, 2006.

  1. Ben

    Ben Guest

    Hello...

    I have a dictionary, where each value is a seperate instance of the
    same class:

    self.mop_list[record_number]=record(self.mops[:])

    In my case I know that record_number takes the values 0,3,and 7 thanks
    to a loop, giving me three instances of the record class instantiaterd
    with some data I've pased in.

    The record object contains a list, and each element of that list should
    also contain an instance of a class should I append one to it:


    self.mop_list[record_number].my_list.append(my_class(0,0,0,self.mop_list[record_no].mops,0))

    So within each record class I have access to any number of my_class
    instances, depending on how many times I have appended:

    self.mop_list[record_number].my_list[index]


    This seems to work without any errors. But bizzarely I find that
    whatever my record number, the instance of "my_class" is appended to
    every list. So in this case

    self.mop_list[0].my_list.append(my_class(Some data for the
    constructor))

    I would expect to append an instance of my_class to
    self.mop_list[0].my_list

    But annoyingly

    self.mop_list[0].my_list
    self.mop_list[3].my_list
    self.mop_list[7].my_list

    all have an instance of my_class created and appended to them. This is
    really confusing and quite annoying - I don't know whether anyone out
    there can make head or tail of what I'm doing wrong?

    Cheers,

    Ben
     
    Ben, Dec 28, 2006
    #1
    1. Advertising

  2. Ben

    Erik Johnson Guest

    "Ben" <> wrote in message
    news:...

    <snip>

    > This seems to work without any errors. But bizzarely I find that
    > whatever my record number, the instance of "my_class" is appended to
    > every list. So in this case
    >
    > self.mop_list[0].my_list.append(my_class(Some data for the
    > constructor))
    >
    > I would expect to append an instance of my_class to
    > self.mop_list[0].my_list
    >
    > But annoyingly
    >
    > self.mop_list[0].my_list
    > self.mop_list[3].my_list
    > self.mop_list[7].my_list
    >
    > all have an instance of my_class created and appended to them. This is
    > really confusing and quite annoying - I don't know whether anyone out
    > there can make head or tail of what I'm doing wrong?


    Well, it's a little bit difficult, but I think I actually know what's going
    on. You probably need some code that looks something like this, to ensure
    each object has it's own, independent list:

    class record:
    def __init__(self, init_list=None):
    self.my_list = []
    if init_list is not None:
    self.my_list.extend(init_list)


    Here's what I think you are doing, and below should make it clear why that
    doesn't work:

    class record:
    def __init__(self, init_list=[]):

    That list above, the default initializer is constructed just once (when the
    def statement executes)!

    >>> class record:

    .... def __init__(self, init_list=[]):
    .... self.my_list = init_list
    ....
    >>> r1 = record()
    >>> r1.my_list

    []
    >>> r2 = record()
    >>> r2.my_list

    []
    >>> r2.my_list.append('boo!')
    >>> r1.my_list

    ['boo!']
    >>>
    >>> l1 = range(1, 4)
    >>> l1

    [1, 2, 3]
    >>> r1 = record(l1)
    >>> r2 = record(l1)
    >>> r1.my_list

    [1, 2, 3]
    >>> r2.my_list

    [1, 2, 3]
    >>> r1.my_list.append(42)
    >>> l1

    [1, 2, 3, 42]
    >>> r1.my_list

    [1, 2, 3, 42]
    >>> r2.my_list

    [1, 2, 3, 42]
    >>>
     
    Erik Johnson, Dec 28, 2006
    #2
    1. Advertising

  3. Ben

    Ben Guest

    Ah - ok. In fact I simply had:

    class record:
    my_list =[]
    mops=[]

    def __init__(self,mops):
    self.mops=mops

    Where mops is something I pass in when i create the instance. I had
    thought that then each time I created an instance of the record class
    as an element of my dictionary:

    self.mop_list[record_number]=record(self.mops[:])

    I would create a brand new instance of the record class.It would have a
    mops list initialized with mops (because the def__init__ constructor is
    called when the class is instantiated), and an empty, individual list
    my_list.

    I could then access each individual list by doing:

    self.mop_list[x].my_list[y]=something

    But in fact the same my_list is being accessed for all values of x, so
    a change to one list is in fact a change to them all. I think you might
    be right, but can't quite work it out myself! I'll keep trying :)

    Thanks for your help,

    Ben





    Erik Johnson wrote:

    > "Ben" <> wrote in message
    > news:...
    >
    > <snip>
    >
    > > This seems to work without any errors. But bizzarely I find that
    > > whatever my record number, the instance of "my_class" is appended to
    > > every list. So in this case
    > >
    > > self.mop_list[0].my_list.append(my_class(Some data for the
    > > constructor))
    > >
    > > I would expect to append an instance of my_class to
    > > self.mop_list[0].my_list
    > >
    > > But annoyingly
    > >
    > > self.mop_list[0].my_list
    > > self.mop_list[3].my_list
    > > self.mop_list[7].my_list
    > >
    > > all have an instance of my_class created and appended to them. This is
    > > really confusing and quite annoying - I don't know whether anyone out
    > > there can make head or tail of what I'm doing wrong?

    >
    > Well, it's a little bit difficult, but I think I actually know what's going
    > on. You probably need some code that looks something like this, to ensure
    > each object has it's own, independent list:
    >
    > class record:
    > def __init__(self, init_list=None):
    > self.my_list = []
    > if init_list is not None:
    > self.my_list.extend(init_list)
    >
    >
    > Here's what I think you are doing, and below should make it clear why that
    > doesn't work:
    >
    > class record:
    > def __init__(self, init_list=[]):
    >
    > That list above, the default initializer is constructed just once (when the
    > def statement executes)!
    >
    > >>> class record:

    > ... def __init__(self, init_list=[]):
    > ... self.my_list = init_list
    > ...
    > >>> r1 = record()
    > >>> r1.my_list

    > []
    > >>> r2 = record()
    > >>> r2.my_list

    > []
    > >>> r2.my_list.append('boo!')
    > >>> r1.my_list

    > ['boo!']
    > >>>
    > >>> l1 = range(1, 4)
    > >>> l1

    > [1, 2, 3]
    > >>> r1 = record(l1)
    > >>> r2 = record(l1)
    > >>> r1.my_list

    > [1, 2, 3]
    > >>> r2.my_list

    > [1, 2, 3]
    > >>> r1.my_list.append(42)
    > >>> l1

    > [1, 2, 3, 42]
    > >>> r1.my_list

    > [1, 2, 3, 42]
    > >>> r2.my_list

    > [1, 2, 3, 42]
    > >>>
     
    Ben, Dec 28, 2006
    #3
  4. Ben

    Ben Guest

    Ah - I have been very silly indeed!

    I had not realised that by creating my lists outside of the
    def__init___() constructor they would always be class rather than
    instance variables, and so there was only one of them when I referred
    to it. Thanks to Erik and Chris for your help!

    Ben


    Ben wrote:

    > Ah - ok. In fact I simply had:
    >
    > class record:
    > my_list =[]
    > mops=[]
    >
    > def __init__(self,mops):
    > self.mops=mops
    >
    > Where mops is something I pass in when i create the instance. I had
    > thought that then each time I created an instance of the record class
    > as an element of my dictionary:
    >
    > self.mop_list[record_number]=record(self.mops[:])
    >
    > I would create a brand new instance of the record class.It would have a
    > mops list initialized with mops (because the def__init__ constructor is
    > called when the class is instantiated), and an empty, individual list
    > my_list.
    >
    > I could then access each individual list by doing:
    >
    > self.mop_list[x].my_list[y]=something
    >
    > But in fact the same my_list is being accessed for all values of x, so
    > a change to one list is in fact a change to them all. I think you might
    > be right, but can't quite work it out myself! I'll keep trying :)
    >
    > Thanks for your help,
    >
    > Ben
    >
    >
    >
    >
    >
    > Erik Johnson wrote:
    >
    > > "Ben" <> wrote in message
    > > news:...
    > >
    > > <snip>
    > >
    > > > This seems to work without any errors. But bizzarely I find that
    > > > whatever my record number, the instance of "my_class" is appended to
    > > > every list. So in this case
    > > >
    > > > self.mop_list[0].my_list.append(my_class(Some data for the
    > > > constructor))
    > > >
    > > > I would expect to append an instance of my_class to
    > > > self.mop_list[0].my_list
    > > >
    > > > But annoyingly
    > > >
    > > > self.mop_list[0].my_list
    > > > self.mop_list[3].my_list
    > > > self.mop_list[7].my_list
    > > >
    > > > all have an instance of my_class created and appended to them. This is
    > > > really confusing and quite annoying - I don't know whether anyone out
    > > > there can make head or tail of what I'm doing wrong?

    > >
    > > Well, it's a little bit difficult, but I think I actually know what's going
    > > on. You probably need some code that looks something like this, to ensure
    > > each object has it's own, independent list:
    > >
    > > class record:
    > > def __init__(self, init_list=None):
    > > self.my_list = []
    > > if init_list is not None:
    > > self.my_list.extend(init_list)
    > >
    > >
    > > Here's what I think you are doing, and below should make it clear why that
    > > doesn't work:
    > >
    > > class record:
    > > def __init__(self, init_list=[]):
    > >
    > > That list above, the default initializer is constructed just once (when the
    > > def statement executes)!
    > >
    > > >>> class record:

    > > ... def __init__(self, init_list=[]):
    > > ... self.my_list = init_list
    > > ...
    > > >>> r1 = record()
    > > >>> r1.my_list

    > > []
    > > >>> r2 = record()
    > > >>> r2.my_list

    > > []
    > > >>> r2.my_list.append('boo!')
    > > >>> r1.my_list

    > > ['boo!']
    > > >>>
    > > >>> l1 = range(1, 4)
    > > >>> l1

    > > [1, 2, 3]
    > > >>> r1 = record(l1)
    > > >>> r2 = record(l1)
    > > >>> r1.my_list

    > > [1, 2, 3]
    > > >>> r2.my_list

    > > [1, 2, 3]
    > > >>> r1.my_list.append(42)
    > > >>> l1

    > > [1, 2, 3, 42]
    > > >>> r1.my_list

    > > [1, 2, 3, 42]
    > > >>> r2.my_list

    > > [1, 2, 3, 42]
    > > >>>
     
    Ben, Dec 28, 2006
    #4
  5. Ben

    Erik Johnson Guest

    "Ben" <> wrote in message
    news:...

    > class record:
    > my_list =[]
    > mops=[]
    >
    > def __init__(self,mops):
    > self.mops=mops


    Similar to the example I gave, the lists my_list and mops shown above are
    executed just once: when your class definition is first parsed.
    The statement:

    def __init__(self,mops):

    is also executed just once, and the value for mops at that time is the value
    assigned to object attributes during object construction - a reference to
    record.mops, in your case. So, there are really only two lists here, class
    attributes record.my_list and record.mops. Each of your constructed objects
    is assigned a reference to record.mops. They all share that list. If you
    want a record object to have it's own list, give it a new, empty one and
    then populate it appropriately.
     
    Erik Johnson, Dec 28, 2006
    #5
  6. Ben

    Ben Guest

    Yes- I can see that my_list and mops, being outside def __init__()
    only get created once, hence the confusion.

    Surely def __init__() gets called each time an instance is created
    however? But the snag is that if they have default values set these are
    shared between all instances, even if they are, for instance, lists...?

    Cheers,

    Ben


    Thanks.
    Erik Johnson wrote:

    > "Ben" <> wrote in message
    > news:...
    >
    > > class record:
    > > my_list =[]
    > > mops=[]
    > >
    > > def __init__(self,mops):
    > > self.mops=mops

    >
    > Similar to the example I gave, the lists my_list and mops shown above are
    > executed just once: when your class definition is first parsed.
    > The statement:
    >
    > def __init__(self,mops):
    >
    > is also executed just once, and the value for mops at that time is the value
    > assigned to object attributes during object construction - a reference to
    > record.mops, in your case. So, there are really only two lists here, class
    > attributes record.my_list and record.mops. Each of your constructed objects
    > is assigned a reference to record.mops. They all share that list. If you
    > want a record object to have it's own list, give it a new, empty one and
    > then populate it appropriately.
     
    Ben, Dec 28, 2006
    #6
  7. Ben

    Erik Johnson Guest

    "Ben" <> wrote in message
    news:...

    > Yes- I can see that my_list and mops, being outside def __init__()
    > only get created once, hence the confusion.
    >
    > Surely def __init__() gets called each time an instance is created
    > however? But the snag is that if they have default values set these are
    > shared between all instances, even if they are, for instance, lists...?


    I don't think I quite understand the confusion, and I think I might have
    said something misleading. The def statement itself is only executed once,
    as are any object initializers contained within it. Your record.__init__()
    is not using a default initializer, and here:

    class record:
    my_list =[]
    mops=[]

    def __init__(self,mops):
    self.mops=mops


    mops as the second argument to __init__ is a formal vairable, not a
    reference to record.mops. If you pass the same list reference to each
    constructor, then each record object shares the same list.

    You previously said:

    I had thought that then each time I created an instance of the record class
    as an element of my dictionary:

    self.mop_list[record_number]=record(self.mops[:])

    I would create a brand new instance of the record class.It would have a
    mops list initialized with mops (because the def__init__ constructor is
    called when the class is instantiated), and an empty, individual list
    my_list.

    I beleive this is basically correct - by using the slice notation in

    self.mops[:]

    you are passing a copy of that list as the value. The difference is
    demonstrated here:

    >>> class Foo:

    .... X = [99]
    .... def __init__(self, X): # X is formal variable here, not Foo.X!
    .... self.X = X
    ....
    >>> y = [1,2,3]
    >>> f1 = Foo(y)
    >>> f1.X

    [1, 2, 3]
    >>> Foo.X

    [99]
    >>> f1.X.append(42)
    >>> f1.X

    [1, 2, 3, 42]
    >>> y

    [1, 2, 3, 42]
    >>> Foo.X

    [99]
    >>> f2 = Foo(y[:])
    >>> f2.X

    [1, 2, 3, 42]
    >>> y

    [1, 2, 3, 42]
    >>> Foo.X

    [99]
    >>> f2.X.append('something else')
    >>> f2.X

    [1, 2, 3, 42, 'something else']
    >>> f1.X

    [1, 2, 3, 42]
    >>> y

    [1, 2, 3, 42]
    >>> Foo.X

    [99]
    >>>



    So, I guess I don't see exactly where the problem is, and without all
    your source, I am kinda poking in the dark. But from the behaviour observed
    in your record objects, you are obviously ending up with shared list
    references where you intend to get per-object individual lists. Generally
    the way to do that is to assign a new, empty list in the body of the object
    initializer and then populate that list as needed.

    Good luck.
    -ej
     
    Erik Johnson, Dec 28, 2006
    #7
  8. On Thu, 28 Dec 2006 09:16:18 -0800, Ben wrote:

    > Hello...
    >
    > I have a dictionary, where each value is a seperate instance of the
    > same class:
    >
    > self.mop_list[record_number]=record(self.mops[:])


    Others have already solved the immediate problem, but I'd just like to
    make a brief comment about self-documenting code, in particular misleading
    self-documenting code.

    The O.P. says he has a DICTIONARY called mop_LIST.

    [raises eyebrow]



    --
    Steven.
     
    Steven D'Aprano, Dec 28, 2006
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. David Vincent

    unittest behaving oddly

    David Vincent, Jun 20, 2006, in forum: Python
    Replies:
    3
    Views:
    1,060
    Fredrik Lundh
    Jun 20, 2006
  2. Replies:
    8
    Views:
    385
    mlimber
    Dec 8, 2005
  3. Siam
    Replies:
    4
    Views:
    413
    Pete Becker
    Jul 27, 2006
  4. Replies:
    13
    Views:
    194
    Ilya Zakharevich
    Feb 22, 2011
  5. babygodzilla
    Replies:
    4
    Views:
    118
    babygodzilla
    Sep 18, 2007
Loading...

Share This Page