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


Question

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

В некотором смысле я следую за вторичным решением David, где я создаю другую версию Класса, который не отображается.


Я пытался использовать StudentDBRecord решение, упомянутое ниже, но, получило ошибку!

File "Main.py", line 25, in 
    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 "", 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 на самом деле a Project объект, и у меня есть соответствие projects словарь для этого.

Так я deepcopy оба students и projects - который я - он говорит, что у меня будут случаи где students allocated_proj_ref атрибут будет иметь проблемы с соответствием экземплярам в projects словарь.

Таким образом я беру его, что я должен буду переопределить/переопределить (это - то, чем это называют не так ли?) deepcopy в каждом использовании Класса def __deecopy__(self, memo): или что-то как этот?


Я был бы я хотеть переопределить __deepcopy__ таким образом, что это игнорирует весь материал SQLA (которые являются и ) но скопируйте все остальное, что это - часть отображенный класс.

Какие-либо предложения?

9
задан 12 revs 23 May 2017 в 12:33
поделиться

2 ответа

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

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

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

EDIT: Хорошо, уточню, что я имел в виду под последним пунктом: сейчас у вас есть класс 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()

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

EDIT 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
ответ дан 5 December 2019 в 01:42
поделиться

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

  1. Получить объекты из базы данных вместе со всеми необходимыми отношениями. Вы можете передать отношениям lazy = 'connected' или lazy = 'subquery' , либо вызвать options (метод запроса eagerload (Relationship_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',)]
2
ответ дан 5 December 2019 в 01:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: