2010-06-06 21 views
9

Creo que traté de pedir demasiado en my previous question así que me disculpo por eso. Permítanme exponer mi situación de la manera más simple posible esta vez.Ayuda con copia y copia profunda en Python

Básicamente, tengo un montón de diccionarios que hacen referencia a mis objetos, que a su vez se asignan utilizando SQLAlchemy. Todo bien conmigo Sin embargo, quiero hacer cambios iterativos en los contenidos de esos diccionarios. El problema es que al hacerlo cambiarán los objetos a los que hacen referencia --- y usar copy.copy() no sirve de nada ya que solo copia las referencias contenidas dentro del diccionario. Por lo tanto, aunque copie algo, cuando lo intente, diga print el contenido del diccionario, solo obtendré los últimos valores actualizados para el objeto.

Es por eso que quería utilizar copy.deepcopy() pero eso no funciona con SQLAlchemy. Ahora estoy en un dilema ya que necesito copiar ciertos atributos de mi objeto antes de hacer dichos cambios iterativos.

En resumen, necesito usar SQLAlchemy y al mismo tiempo, me aseguro de que puedo tener una copia de los atributos de mis objetos cuando realizo cambios para que no cambie el objeto al que se hace referencia.

¿Algún consejo, ayuda, sugerencias, etc.?


Edit: Han añadido algunos códigos.

class Student(object): 
    def __init__(self, sid, name, allocated_proj_ref, allocated_rank): 
     self.sid = sid 
     self.name = name 
     self.allocated_proj_ref = None 
     self.allocated_rank = None 

students_table = Table('studs', metadata, 
    Column('sid', Integer, primary_key=True), 
    Column('name', String), 
    Column('allocated_proj_ref', Integer, ForeignKey('projs.proj_id')), 
    Column('allocated_rank', Integer) 
) 

mapper(Student, students_table, properties={'proj' : relation(Project)}) 

students = {} 

students[sid] = Student(sid, name, allocated_project, allocated_rank) 

Por lo tanto, los atributos que se van a cambiar son los allocated_proj_ref y allocated_rank atributos. El students_table está codificado con la identificación de estudiante única (sid).


Question

me gustaría que persistan los atributos que cambiar de arriba - es decir, eso es, básicamente, por la que decidí utilizar SQLA. Sin embargo, el objeto mapeado cambiará, lo cual no es recomendable. Por lo tanto, si realizo los cambios en doppelgänger, objeto no mapeado ... puedo tomar esos cambios y actualizar los campos/tabla para el objeto mapeado.

En cierto sentido sigo el secondary solution de David donde creo otra versión de la clase que no está mapeada.


He intentado utilizar la solución StudentDBRecord se menciona más adelante, pero tiene un error!

File "Main.py", line 25, in <module> 
    prefsTableFile = 'Database/prefs-table.txt') 
File "/XXXX/DataReader.py", line 158, in readData 
readProjectsFile(projectsFile) 
File "/XXXX/DataReader.py", line 66, in readProjectsFile 
supervisors[ee_id] = Supervisor(ee_id, name, original_quota, loading_limit) 
File "<string>", line 4, in __init__ 
raise exc.UnmappedClassError(class_) 
sqlalchemy.orm.exc.UnmappedClassError: Class 'ProjectParties.Student' is not mapped 

¿Quiere decir esto que Student debe ser mapeadas?


Health warning!

Alguien señaló un muy buen tema adicional aquí. Mira, incluso si llamo al copy.deepcopy() en un objeto no mapeado, en este caso, asumamos que es el diccionario de estudiantes que he definido anteriormente, deepcopy hace una copia de todo.Mi allocated_proj_ref es en realidad un objeto Project, y tengo un diccionario correspondiente projects para eso.

Así que DeepCopy tanto students y projects - la cual soy - dice que voy a tener casos en los que el atributo allocated_proj_refstudents 's va a tener problemas con el juego con casos en el diccionario projects.

Por lo tanto, supongo que tendré que redefinir/anular (así se llama, ¿no?) deepcopy en cada clase utilizando def __deecopy__(self, memo): o algo así?


Me Me gustaría anular __deepcopy__ tal que no tiene en cuenta todas las cosas SQLA (que son <class 'sqlalchemy.util.symbol'> y <class 'sqlalchemy.orm.state.InstanceState'>), pero copia todo lo demás que es parte de la clase asignada.

¿Alguna sugerencia, por favor?

+0

Al modificar los objetos mapeados, ¿desea que los originales o las versiones modificadas a persistir? –

+0

@ Winston: O bien persisten los originales o bien persiste una versión modificada específica ('best' - ver ediciones). Ya puedo ver problemas con este último ya que 'best' no se queda quieto ya que estoy actualizando el objeto en sí. Suspiro. – PizzAzzra

+0

En respuesta a sus ediciones: ¿por qué diablos está usando una base de datos para hacer esto? Tendría más sentido tener 'Estudiante' como una clase de Python normal, y almacenarlo en una base de datos (o archivo) solo después de que encuentre el óptimo global. –

Respuesta

1

Si estoy recordando/pensando correctamente, en SQLAlchemy normalmente solo tiene un objeto a la vez que corresponde a un registro de base de datos dado. Esto se hace para que SQLAlchemy pueda mantener sus objetos de Python sincronizados con la base de datos, y viceversa (bueno, no si hay mutaciones de DB concurrentes desde fuera de Python, pero esa es otra historia). Entonces, el problema es que, si copiara uno de estos objetos mapeados, terminaría con dos objetos distintos que corresponden al mismo registro de la base de datos. Si cambia uno, entonces tendrían valores diferentes, y la base de datos no puede hacer coincidir ambos al mismo tiempo.

Creo que lo que debe hacer es decidir si desea que el registro de la base de datos refleje los cambios que realiza cuando cambia un atributo de su copia. Si es así, entonces no deberías estar copiando los objetos, solo deberías reutilizar las mismas instancias.

Por otro lado, si no desea que el registro de la base de datos original cambie cuando actualiza la copia, tiene otra opción: ¿debería convertirse la copia en una nueva fila en la base de datos? ¿O no debería asignarse a un registro de base de datos? En el primer caso, puede implementar la operación de copia creando una nueva instancia de la misma clase y copiando los valores, más o menos de la misma manera que creó el objeto original. Esto probablemente se haría en el método __deepcopy__() de su clase mapeada SQLAlchemy. En el último caso (sin mapeo), necesitaría una clase separada que tenga todos los mismos campos pero no se mapee con SQLAlchemy. En realidad, probablemente tendría más sentido que tu clase SQLAlchemy-mapped sea una subclase de esta clase no mapeada, y solo haga la asignación para la subclase.

EDITAR: OK, para aclarar lo que quería decir con este último punto: en este momento tiene una clase Student que se utiliza para representar a sus estudiantes. Lo que estoy sugiriendo es que se haga una Student no asignada, clase regular:

class Student(object): 
    def __init__(self, sid, name, allocated_proj_ref, allocated_rank): 
     self.sid = sid 
     self.name = name 
     self.allocated_project = None 
     self.allocated_rank = None 

y tienen una subclase, algo así como StudentDBRecord, que va a proyectar en la base de datos.

class StudentDBRecord(Student): 
    def __init__(self, student): 
     super(StudentDBRecord, self).__init__(student.sid, student.name, 
      student.allocated_proj_ref, student.allocated_rank) 

# this call remains the same 
students_table = Table('studs', metadata, 
    Column('sid', Integer, primary_key=True), 
    Column('name', String), 
    Column('allocated_proj_ref', Integer, ForeignKey('projs.proj_id')), 
    Column('allocated_rank', Integer) 
) 

# this changes 
mapper(StudentDBRecord, students_table, properties={'proj' : relation(Project)}) 

Ahora sería implementar su algoritmo de optimización de uso de instancias de Student, que están sin asignar - así como los atributos de los objetos cambian Student, no pasa nada a la base de datos. Esto significa que puede usar de manera segura copy o deepcopy según sea necesario.Cuando ya está todo hecho, puede cambiar los Student casos a StudentDBRecord casos, algo así como

students = ...dict with best solution... 
student_records = [StudentDBRecord(s) for s in students.itervalues()] 
session.commit() 

Esto creará objetos asignados correspondientes a todos los estudiantes en su estado óptimo y las aprenden de la base de datos.

EDIT 2: Entonces, quizás eso no funcione. Una solución rápida sería copiar el constructor Student en StudentDBRecord y hacer que StudentDBRecord extienda object en su lugar. Es decir, sustituir la definición previa de StudentDBRecord con esto:

class StudentDBRecord(object): 
    def __init__(self, student): 
     self.sid = student.sid 
     self.name = student.name 
     self.allocated_project = student.allocated_project 
     self.allocated_rank = student.allocated_rank 

O si desea generalizar:

class StudentDBRecord(object): 
    def __init__(self, student): 
     for attr in dir(student): 
      if not attr.startswith('__'): 
       setattr(self, attr, getattr(student, attr)) 

Esta última definición copiará todas las propiedades que no sea especial del Student a la StudentDBRecord.

+0

@David: Acabo de actualizar mi pregunta con un ejemplo de una de las clases. "... si no quiere que cambie el registro de la base de datos original cuando actualiza la copia", eso es lo que estoy buscando. ¿Podría describir exactamente qué quiere decir con "' puede implementar la operación de copia creando una nueva instancia de la misma clase y copiando los valores' "? ¿Tengo algún tipo de definición de una versión "localizada" de '__deepcopy __()' para mi clase mapeada SQLA? Creo que entiendo lo que quieres decir con la última línea, pero ¿podrías aclararlo un poco más concretamente? Tal vez un ejemplo? – PizzAzzra

+0

Lo que quise decir es algo como 'def __deepcopy __ (self, memo): return Student (deepcopy (self.sid, memo), deepcopy (self.name, memo), deepcopy (self.located_project, memo), deepcopy (self. assigned_rank, memo)) '(tenga en cuenta que ya no podrá usar' sid' como clave principal si lo hace). –

+0

@David: he actualizado la pregunta. El método '__deepcopy __()' es un poco demasiado para mí, ya que estoy terminando las cosas, sin embargo, he seguido de alguna manera su solución de "campos separados por clases separadas". ¿Podría explicar qué quiere decir por subclase en este caso? – PizzAzzra

2

Aquí es otra opción, pero no estoy seguro de que es aplicable a su problema:

  1. recuperar objetos de la base de datos junto con todas las relaciones necesarias. Puede pasar lazy='joined' o lazy='subquery' a relaciones, o llamar al options(eagerload(relation_property) método de consulta, o simplemente acceder a las propiedades requeridas para activar su carga.
  2. Eliminar el objeto de la sesión. La carga lenta de las propiedades del objeto no será compatible desde este punto.
  3. Ahora puede modificar el objeto de forma segura.
  4. Cuando necesite actualizar el objeto en la base de datos, debe fusionarlo nuevamente en la sesión y confirmar.

actualización: Aquí es demostrar el concepto de código de ejemplo:

from sqlalchemy import * 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import sessionmaker, relation, eagerload 

metadata = MetaData() 
Base = declarative_base(metadata=metadata, name='Base') 

class Project(Base): 
    __tablename__ = 'projects' 
    id = Column(Integer, primary_key=True) 
    name = Column(String) 


class Student(Base): 
    __tablename__ = 'students' 
    id = Column(Integer, primary_key=True) 
    project_id = Column(ForeignKey(Project.id)) 
    project = relation(Project, 
         cascade='save-update, expunge, merge', 
         lazy='joined') 

engine = create_engine('sqlite://', echo=True) 
metadata.create_all(engine) 
session = sessionmaker(bind=engine)() 

proj = Project(name='a') 
stud = Student(project=proj) 
session.add(stud) 
session.commit() 
session.expunge_all() 
assert session.query(Project.name).all()==[('a',)] 

stud = session.query(Student).first() 
# Use options() method if you didn't specify lazy for relations: 
#stud = session.query(Student).options(eagerload(Student.project)).first() 
session.expunge(stud) 

assert stud not in session 
assert stud.project not in session 

stud.project.name = 'b' 
session.commit() # Stores nothing 
assert session.query(Project.name).all()==[('a',)] 

stud = session.merge(stud) 
session.commit() 
assert session.query(Project.name).all()==[('b',)] 
+0

No tengo mucha experiencia con SQLA ... ¿sería posible darme un ejemplo más específico de este método? – PizzAzzra

+0

Lo tengo trabajando ahora con una combinación de una clase mapeada y no mapeada. No es tan elegante como para hacer un seguimiento de mí. Sin embargo, me esfuerzo por dar una solución a su solución (y mantenerla como referencia) en el futuro. Gracias por la ayuda :) – PizzAzzra

Cuestiones relacionadas