Creando una lista de objetos en Python

83

Estoy intentando crear un script de Python que abra varias bases de datos y compare su contenido. En el proceso de crear ese script, me encontré con un problema al crear una lista cuyo contenido son objetos que he creado.

He simplificado el programa al mínimo para esta publicación. Primero creo una nueva clase, creo una nueva instancia de ella, le asigno un atributo y luego lo escribo en una lista. Luego asigno un nuevo valor a la instancia y nuevamente lo escribo en una lista ... y una y otra vez ...

El problema es que siempre es el mismo objeto, así que en realidad solo estoy cambiando el objeto base. Cuando leo la lista, obtengo una repetición del mismo objeto una y otra vez.

Entonces, ¿cómo se escriben objetos en una lista dentro de un bucle?

Aquí está mi código simplificado

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

Entonces, ¿cómo puedo (agregar, extender, copiar o lo que sea) los elementos de simpleList para que cada entrada contenga una instancia diferente del objeto en lugar de que todos apunten al mismo?

anon
fuente
2
Le sugiero que use iteradores en lugar de contadores: "para el elemento en lista simple:" se ve mucho mejor.
Mapad

Respuestas:

70

Demuestra un malentendido fundamental.

Nunca creó una instancia de SimpleClass en absoluto, porque no la llamó.

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

O, si deja que la clase tome parámetros, en su lugar, puede usar una lista de comprensión.

simplelist = [SimpleClass(count) for count in xrange(4)]
ranita de hierro
fuente
2
No disponible para python 3.x - la función xrange () ya no es compatible.
r3t40
3
@TitPoplatnik Simplemente reemplace xrange () por range ().
eXPRESS
@ironfroggy es posible obtener el índice en el que se almacena el objeto desde dentro de la clase. Digamos que si la clase A está en la posición 3 en alguna lista, [x, x, x, A, x], ¿es posible recuperar la posición implementando el método del domo en A.
Alexander Cska
55

Para llenar una lista con instancias separadas de una clase, puede usar un bucle for en la declaración de la lista. El * multiplica vinculará cada copia a la misma instancia.

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

y luego acceder a las instancias a través del índice de la lista.

instancelist[5].attr1 = 'whamma'
Zoomulator
fuente
3
mejor sea: instancelist = [MyClass () for _ in range (29)]
tutormike
11

No debería ser necesario volver a crear el objeto SimpleClass cada vez, como algunos sugieren, si simplemente lo está utilizando para generar datos en función de sus atributos. Sin embargo, en realidad no está creando una instancia de la clase; simplemente está creando una referencia al objeto de clase en sí. Por lo tanto, está agregando una referencia al mismo atributo de clase a la lista (en lugar del atributo de instancia), una y otra vez.

En vez de:

x = SimpleClass

necesitas:

x = SimpleClass()
Daniel Naab
fuente
7
x = SimpleClass() está recreando el objeto cada vez.
neil
5

Cree una nueva instancia cada vez, donde cada nueva instancia tenga el estado correcto, en lugar de modificar continuamente el estado de la misma instancia.

Alternativamente, almacene una copia hecha explícitamente del objeto (usando la sugerencia en esta página ) en cada paso, en lugar del original.

joel.neely
fuente
3

Si entiendo correctamente tu pregunta, me preguntas una forma de ejecutar una copia profunda de un objeto. ¿Qué pasa con el uso de copy.deepcopy?

import copy

x = SimpleClass()

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

Una copia profunda es una copia recursiva de todo el objeto. Para obtener más información, puede consultar la documentación de Python: https://docs.python.org/2/library/copy.html

Roberto Liffredo
fuente
2
Reutilizar un objeto (copia profunda o clon) es una confusión común de n00b. A veces no puede responder la pregunta que parece hacer; a veces tienes que responder a la pregunta que deberían haber hecho.
S.Lott
3

Creo que esto simplemente demuestra lo que está tratando de lograr:

# 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

Ejecute el código anterior y obtendrá: -

[<__main__.Class instance at 0x6311b2c>, 
<__main__.Class instance at 0x63117ec>]
0 uno
1 duo
2 ['uno', 'duo']
Triple Decker
fuente