在Python中帮助复制和深度复制

时间:2010-06-06 06:00:06

标签: python copy sqlalchemy

我认为我试图在my previous question中提出太多要求,为此道歉。让我以这种方式尽可能简单地阐述我的情况。

基本上,我有一堆字典引用我的对象,然后使用SQLAlchemy进行映射。一切都很好。但是,我想对这些词典的内容进行迭代更改。问题是,这样做会改变它们引用的对象---并且使用copy.copy()没有任何好处,因为它只复制字典中包含的引用。因此,即使复制了某些内容,当我尝试使用print字典的内容时,我也只会获得该对象的最新更新值。

这就是为什么我想使用copy.deepcopy(),但这不适用于SQLAlchemy。现在我处于两难境地,因为我需要在进行迭代更改之前复制对象的某些属性。

总之,我需要同时使用SQLAlchemy ,以确保在进行更改时我可以拥有对象属性的副本,因此我不会更改引用的对象本身。

任何建议,帮助,建议等?


Edit:添加了一些代码。

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)

因此,我将要更改的属性是allocated_proj_refallocated_rank属性。使用唯一的学生ID(students_table)键入sid


Question

我想要坚持上面改变的属性 - 我的意思是,这基本上就是我决定使用SQLA的原因。但是,映射的对象将会更改,不建议这样做。因此,如果我对doppelgänger进行了更改,未映射的对象...我可以进行这些更改并更新映射对象的字段/表。

从某种意义上说,我正在关注David的secondary solution,在那里我创建了另一个未映射的Class版本。


我尝试使用下面提到的StudentDBRecord解决方案,但收到了错误!

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

这是否意味着Student 必须映射?


Health warning!

有人在这里指出了一个非常好的附加问题。看,即使我在非映射对象上调用copy.deepcopy(),在这种情况下,让我们假设它是我上面定义的学生词典,deepcopy会复制所有内容。我的allocated_proj_ref实际上是一个Project对象,我有一个相应的projects字典。

因此,我对studentsprojects进行了深入审核 - 我是 - 他说我会遇到students的{​​{1}}属性存在问题的情况与allocated_proj_ref字典中的实例匹配。

因此,我认为我必须重新定义/覆盖(这就是所谓的不是吗?)projects在每个类中使用deepcopy或类似的东西?


我想覆盖def __deecopy__(self, memo):,忽略所有SQLA内容(__deepcopy__<class 'sqlalchemy.util.symbol'>),但复制其他所有映射的内容类。

有什么建议吗?

2 个答案:

答案 0 :(得分:2)

这是另一种选择,但我不确定它是否适用于您的问题:

  1. 从数据库中检索对象以及所有需要的关系。您可以将lazy='joined'lazy='subquery'传递给关系,也可以调用options(eagerload(relation_property)查询方法,或只访问所需的属性以触发其加载。
  2. 从会话中删除对象。此时不支持延迟加载对象属性。
  3. 现在您可以安全地修改对象。
  4. 当您需要更新数据库中的对象时,您必须将其合并回会话并提交。
  5. 更新:以下是概念代码示例的证明:

    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',)]
    

答案 1 :(得分:1)

如果我记得/正确思考,在SQLAlchemy中,您通常只有一个对象与给定数据库记录对应的对象。这样做是为了让SQLAlchemy可以让你的Python对象与数据库保持同步,反之亦然(好吧,如果有来自Python之外的并发数据库突变,那就不是了,但这是另一个故事)。所以问题是,如果你要复制其中一个映射对象,你最终会得到两个与同一个数据库记录对应的不同对象。如果你更改了一个,那么它们将具有不同的值,并且数据库不能同时匹配它们。

我认为您可能需要做的是决定是否希望数据库记录反映您在更改副本属性时所做的更改。如果是这样,那么你根本不应该复制对象,你应该重复使用相同的实例。

另一方面,如果您不希望在更新副本时更改原始数据库记录,则还有另一种选择:副本是否应成为数据库中的新行?或者根本不应该映射到数据库记录?在前一种情况下,您可以通过创建同一个类的新实例并复制值来实现复制操作,这与创建原始对象的方式非常相似。这可能是在SQLAlchemy映射类的__deepcopy__()方法中完成的。在后一种情况下(没有映射),您需要一个具有所有相同字段但不使用SQLAlchemy映射的单独类。实际上,让你的SQLAlchemy映射类成为这个非映射类的子类,并且只对子类进行映射可能更有意义。

编辑:好的,澄清我最后一点的意思:现在你有一个Student课,用来代表你的学生。我建议您将Student设为未映射的常规类:

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

并且有一个类似StudentDBRecord的子类,它将被映射到数据库。

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)})

现在,您将使用未映射的Student实例来实现优化算法 - 以便Student对象的属性发生更改,数据库不会发生任何变化。这意味着您可以根据需要安全地使用copydeepcopy。完成所有操作后,您可以将Student个实例更改为StudentDBRecord个实例,例如

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

这将创建与所有处于最佳状态的学生相对应的映射对象,并将其提交到数据库。

编辑2 :所以也许这不起作用。快速解决方法是将Student构造函数复制到StudentDBRecord并将StudentDBRecord扩展为object。也就是说,将StudentDBRecord的先前定义替换为:

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

或者如果你想概括它:

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

后一个定义会将Student的所有非特殊属性复制到StudentDBRecord