Py学习  »  Python

python中的copy和deepcopy帮助

PizzAzzra • 6 年前 • 1729 次点击  

我想我试着要求太多了 my previous question 为此道歉。这次让我以尽可能简单的方式阐述我的处境。

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

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

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

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


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_ref allocated_rank 属性。这个 students_table 使用唯一的学生ID键入( sid )


Question

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

从某种意义上说,我是跟随大卫的 secondary solution 在这里我创建了另一个未映射的类版本。


我试过用 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复制了 一切 . 我的 分配的项目参考 实际上是 Project 对象,我有一个对应的 projects 字典。

所以我把两者都复制了 students 项目 --我是——他说我会有 学生 分配的项目参考 属性与中的实例匹配时将出现问题 项目 字典。

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


我想重写一下 __deepcopy__ 这样它就忽略了所有的sqla内容(即 <class 'sqlalchemy.util.symbol'> <class 'sqlalchemy.orm.state.InstanceState'> )但复制属于映射类的所有其他内容。

有什么建议吗?

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/30929
 
1729 次点击  
文章 [ 2 ]  |  最新文章 6 年前
Denis Otkidach
Reply   •   1 楼
Denis Otkidach    15 年前

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

  1. 从数据库中检索对象以及所有需要的关系。你也可以通过 lazy='joined' lazy='subquery' 关系或电话 options(eagerload(relation_property) 查询方法,或者只访问触发其加载所需的属性。
  2. 从会话中删除对象。此时将不支持延迟加载对象属性。
  3. 现在您可以安全地修改对象了。
  4. 当需要更新数据库中的对象时,必须将其合并回会话并提交。

更新 :这里是概念代码示例的证明:

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',)]
David Z
Reply   •   2 楼
David Z    15 年前

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

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

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

编辑 :好的,澄清我最后一点的意思:现在你有一个 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)})

现在,您将使用以下实例实现优化算法 学生 ,它是未映射的-例如 学生 对象发生更改,数据库不会发生任何变化。这意味着你可以安全使用 copy deepcopy 根据需要。完成后,您可以更改 学生 实例到 学生数据库记录 实例,比如

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

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

编辑2 :所以这可能不起作用。快速解决方法是复制 学生 构造函数转换为 学生数据库记录 并使 学生数据库记录 延伸 object 相反。也就是说,替换以前的定义 学生数据库记录 用这个:

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

后一个定义将复制 学生 学生数据库记录 .