Help with copy and deepcopy in Python
我想我以前的问题问得太多了,所以为此道歉。这次让我以尽可能简单的方式阐述我的处境。
基本上,我有一堆引用我的对象的字典,它们依次使用sqlacalchemy映射。我没事。但是,我想对那些字典的内容进行迭代更改。问题是这样做会改变它们所引用的对象---使用copy.copy()没有任何好处,因为它只复制字典中包含的引用。因此,即使复制了一些内容,当我试图(比如
这就是为什么我想使用copy.deepcopy(),但这不适用于sqlacalchemy。现在我陷入了困境,因为我需要在进行上述迭代更改之前复制对象的某些属性。
总之,我需要使用sqlacalchemy,同时确保在进行更改时可以拥有对象属性的副本,这样就不会更改引用的对象本身。
有什么建议、帮助、建议等吗?
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) |
因此,我将要更改的属性是
我想保留上面更改的属性——我的意思是,这就是我决定使用sqla的基本原因。但是,映射对象将更改,这是不推荐的。因此,如果我对doppelg进行更改?NGE,未映射对象…我可以接受这些更改并更新映射对象的字段/表吗?
在某种意义上,我在遵循大卫的第二个解决方案,在这里我创建了未映射的类的另一个版本。
我尝试使用下面提到的
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 |
这是否意味着必须映射
有人在这里指出了一个很好的附加问题。看,即使我在一个非映射对象上调用
所以我把
因此,我认为我必须重新定义/重写(这就是它所称的,不是吗?)每个类中使用
我想重写
有什么建议吗?
这是另一种选择,但我不确定它是否适用于您的问题:
更新:这里是概念代码示例的证明:
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映射类的
编辑:好的,澄清我最后一点的意思:现在你有一个用来代表学生的
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 |
并且有一个子类,比如
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)}) |
现在,您将使用未映射的
1 2 3 | students = ...dict with best solution... student_records = [StudentDBRecord(s) for s in students.itervalues()] session.commit() |
这将创建对应于处于最佳状态的所有学生的映射对象,并将其提交到数据库。
编辑2:所以这可能行不通。快速修复方法是将
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)) |
后一个定义将把