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

Help with copy and deepcopy in Python

我想我以前的问题问得太多了,所以为此道歉。这次让我以尽可能简单的方式阐述我的处境。

基本上,我有一堆引用我的对象的字典,它们依次使用sqlacalchemy映射。我没事。但是,我想对那些字典的内容进行迭代更改。问题是这样做会改变它们所引用的对象---使用copy.copy()没有任何好处,因为它只复制字典中包含的引用。因此,即使复制了一些内容,当我试图(比如print字典的内容)时,我也只能获取对象的最新更新值。

这就是为什么我想使用copy.deepcopy(),但这不适用于sqlacalchemy。现在我陷入了困境,因为我需要在进行上述迭代更改之前复制对象的某些属性。

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

有什么建议、帮助、建议等吗?

Edit:增加了一些代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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(sidstudents_table进行键控。

Question

我想保留上面更改的属性——我的意思是,这就是我决定使用sqla的基本原因。但是,映射对象将更改,这是不推荐的。因此,如果我对doppelg进行更改?NGE,未映射对象…我可以接受这些更改并更新映射对象的字段/表吗?

在某种意义上,我在遵循大卫的第二个解决方案,在这里我创建了未映射的类的另一个版本。

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

1
2
3
4
5
6
7
8
9
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都复制了,他说我会遇到这样的情况:studentsallocated_proj_ref属性与projects字典中的实例匹配有问题。

因此,我认为我必须重新定义/重写(这就是它所称的,不是吗?)每个类中使用def __deecopy__(self, memo):或类似的东西的deepcopy

我想重写__deepcopy__,这样它就忽略了所有的sqla内容(),但复制了属于映射类的所有其他内容。

有什么建议吗?


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    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',)]


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

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

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

    编辑:好的,澄清我最后一点的意思:现在你有一个用来代表学生的Student课程。我建议你把EDOCX1[1]变成一个未映射的常规类:

    1
    2
    3
    4
    5
    6
    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,将映射到数据库。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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实例,类似于

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

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

    编辑2:所以这可能行不通。快速修复方法是将Student构造函数复制到StudentDBRecord中,并使StudentDBRecord改为扩展object。也就是说,用以下内容替换先前对StudentDBRecord的定义:

    1
    2
    3
    4
    5
    6
    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

    或者如果你想概括一下:

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

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