85

I'm trying to create a Python script that opens several databases and compares their contents. In the process of creating that script, I've run into a problem in creating a list whose contents are objects that I've created.

I've simplified the program to its bare bones for this posting. First I create a new class, create a new instance of it, assign it an attribute and then write it to a list. Then I assign a new value to the instance and again write it to a list... and again and again...

Problem is, it's always the same object so I'm really just changing the base object. When I read the list, I get a repeat of the same object over and over.

So how do you write objects to a list within a loop?

Here's my simplified code

class SimpleClass(object):
    pass

x = SimpleClass
# Then create an empty list
simpleList = []
#Then loop through from 0 to 3 adding an attribute to the instance 'x' of SimpleClass
for count in range(0,4):       
    # each iteration creates a slightly different attribute value, and then prints it to
# prove that step is working
# but the problem is, I'm always updating a reference to 'x' and what I want to add to
# simplelist is a new instance of x that contains the updated attribute

x.attr1= '*Bob* '* count
print "Loop Count: %s Attribute Value %s" % (count, x.attr1)
simpleList.append(x)

print '-'*20
# And here I print out each instance of the object stored in the list 'simpleList'
# and the problem surfaces.  Every element of 'simpleList' contains the same      attribute value

y = SimpleClass
print "Reading the attributes from the objects in the list"
for count in range(0,4):
    y = simpleList[count]
    print y.attr1

So how do I (append, extend, copy or whatever) the elements of simpleList so that each entry contains a different instance of the object instead of all pointing to the same one?

Mel
  • 5,837
  • 10
  • 37
  • 42
  • 2
    I suggest you use iterators instead of counters: "for item in simpleList:" looks much better. – Mapad Dec 08 '08 at 09:01

6 Answers6

74

You demonstrate a fundamental misunderstanding.

You never created an instance of SimpleClass at all, because you didn't call it.

for count in xrange(4):
    x = SimpleClass()
    x.attr = count
    simplelist.append(x)

Or, if you let the class take parameters, instead, you can use a list comprehension.

simplelist = [SimpleClass(count) for count in xrange(4)]
ironfroggy
  • 7,991
  • 7
  • 33
  • 44
  • 2
    Not available for python 3.x - xrange() func not supported anymore. – r3t40 Feb 14 '19 at 18:13
  • 3
    @TitPoplatnik Simply replace xrange() for range(). – eXPRESS Apr 20 '19 at 08:00
  • @ironfroggy is it possible to get the index at which the object is stored from within the class. Say if class A is at position 3 in some list, [x,x,x,A,x], is it possible to retrieve the position by implementing dome method in A. – Alexander Cska Jul 03 '19 at 19:32
59

A list comprehension can be used to fill a list with separate instances of a class, like so:

instancelist = [MyClass() for i in range(29)]

This avoids the problem with multiplying a list of one element with *, which re-uses the same object.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Zoomulator
  • 20,774
  • 7
  • 28
  • 32
12

It shouldn't be necessary to recreate the SimpleClass object each time, as some are suggesting, if you're simply using it to output data based on its attributes. However, you're not actually creating an instance of the class; you're simply creating a reference to the class object itself. Therefore, you're adding a reference to the same class attribute to the list (instead of instance attribute), over and over.

Instead of:

x = SimpleClass

you need:

x = SimpleClass()
tzot
  • 92,761
  • 29
  • 141
  • 204
Daniel Naab
  • 22,690
  • 8
  • 54
  • 55
  • 7
    `x = SimpleClass()` _is_ recreating the object every time. – neil Nov 22 '13 at 19:23
  • Thank you so much for this answer, had been frustrated for hours trying to figure out why were all my list of instances created inside the loop referenced the same instance only to realise I was missing parentheses. Can you please provide more information on this? What difference do the parentheses make ? – Shivam Roy Feb 26 '22 at 23:36
5

Create a new instance each time, where each new instance has the correct state, rather than continually modifying the state of the same instance.

Alternately, store an explicitly-made copy of the object (using the hint at this page) at each step, rather than the original.

joel.neely
  • 30,725
  • 9
  • 56
  • 64
3

If I understand correctly your question, you ask a way to execute a deep copy of an object. What about using copy.deepcopy?

import copy

x = SimpleClass()

for count in range(0,4):
  y = copy.deepcopy(x)
  (...)
  y.attr1= '*Bob* '* count

A deepcopy is a recursive copy of the entire object. For more reference, you can have a look at the python documentation: https://docs.python.org/2/library/copy.html

twasbrillig
  • 17,084
  • 9
  • 43
  • 67
rob
  • 36,896
  • 2
  • 55
  • 65
  • 2
    Reusing an object -- deep copy or clone -- is a common n00b confusion. Sometimes you can't answer the question the appear to ask; sometimes you have to answer the question they should have asked. – S.Lott Dec 08 '08 at 12:18
3

I think this simply demonstrates what you are trying to achieve:

# coding: utf-8

class Class():
    count = 0
    names = []

    def __init__(self,name):
        self.number = Class.count
        self.name = name
        Class.count += 1
        Class.names.append(name)

l=[]
l.append(Class("uno"))
l.append(Class("duo"))
print l
print l[0].number, l[0].name
print l[1].number, l[1].name
print Class.count, Class.names

Run the code above and you get:-

[<__main__.Class instance at 0x6311b2c>, 
<__main__.Class instance at 0x63117ec>]
0 uno
1 duo
2 ['uno', 'duo']
Kasia Gogolek
  • 3,374
  • 4
  • 33
  • 50