Помощь с копированием и deepcopy в Python

я думаю, что я пытался задать слишком много мой предыдущий вопрос так что извиняюсь за это. Позвольте мне изложить свою ситуацию как можно проще.

в основном, у меня есть куча словарей, которые ссылаются на мои объекты, которые, в свою очередь, сопоставляются с помощью 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_ref и allocated_rank атрибуты. The students_table используется уникальный идентификатор студента (sid).


Question

я хотел бы сохранить атрибуты, которые я меняю выше - я имею в виду, что в основном поэтому я решил использовать SQLA. Однако сопоставленный объект будет меняться, что не рекомендуется. Таким образом, если я внесу изменения в doppelgänger, unmapped

2 ответов


если я правильно помню / думаю, в SQLAlchemy у вас обычно есть только один объект за раз, который соответствует данной записи базы данных. Это делается для того, чтобы SQLAlchemy могла синхронизировать ваши объекты Python с базой данных и наоборот (ну, не если есть параллельные мутации БД извне Python, но это другая история). Итак, проблема в том, что если вы скопируете один из этих сопоставленных объектов, вы получите два разных объекта, которые соответствуют одному и тому же запись базы данных. Если вы измените один, они будут иметь разные значения, и база данных не может совпадать с обоими одновременно.

я думаю, что вам может потребоваться решить, хотите ли вы, чтобы запись базы данных отражала изменения, которые вы делаете при изменении атрибута вашей копии. Если это так, то вы не должны копировать объекты вообще, вы должны просто повторно использовать те же экземпляры.

С другой стороны, если вам не нужна исходная запись базы данных чтобы изменить при обновлении копии, у вас есть другой выбор: должна ли копия стать новой строкой в базе данных? Или он вообще не должен быть сопоставлен с записью базы данных? В первом случае вы можете реализовать операцию копирования, создав новый экземпляр того же класса и скопировав значения почти так же, как вы создали исходный объект. Это, вероятно, будет сделано в __deepcopy__() метод вашего сопоставленного класса SQLAlchemy. В последнем случае (без сопоставления), вы потребуется отдельный класс, который имеет все те же поля, но не сопоставляется с помощью SQLAlchemy. На самом деле, вероятно, было бы более разумно, чтобы ваш класс SQLAlchemy-mapped был подклассом этого не сопоставленного класса и выполнял только сопоставление для подкласса.

редактировать: хорошо, чтобы уточнить, что я имел в виду под этим последним пунктом: прямо сейчас у вас есть 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 объекты меняются, с базой данных ничего не происходит. Это означает, что вы можете безопасно использовать copy или deepcopy по мере необходимости. Когда вы закончите, вы можете изменить 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.


вот еще один вариант, но я не уверен, что это применимо к вашей проблеме:

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