Попытка засолить неизвестный тип при создании глубокой копии - PullRequest
1 голос
/ 11 июня 2019

У меня есть два следующих класса:

class QPolygonModel(QtGui.QPolygon):
    _idx = None
    _selected = None

    def __init__(self, idx, polygon: QtGui.QPolygon = None):
        # Call default constructor
        if polygon is None:
            super().__init__()
        # Call copy constructor
        else:
            super().__init__(polygon)

        self._idx = idx
        self._selected = False

    @property
    def idx(self):
        return self._idx

    @property
    def is_selected(self):
        return self._selected

    @is_selected.setter
    def is_selected(self, flag):
        self._selected = flag

    def get_points(self):
        res = []
        for i in range(0, self.size()):
            res.append(self.point(i))

        return res

Это пользовательский класс многоугольника, который наследуется от QPolygon.Объекты этого класса хранятся в списке в классе «Scene»:

class ImageAnnotatorState:
    points = None

    radius = None
    image = None

    polygons = None

    _finished = None
    multiselect = None

    def __init__(self, image):
        super().__init__()

        self.points = QtGui.QPolygon()

        self.radius = 8
        self.image = image

        self.polygons = self._init_polygons()

        self.is_finished = False
        self.multiselect = False

    def _init_polygons(self):
        result = []

        for annotation in self.image.annotations:
            polyline = QPolygonModel(annotation.get_id())

            for point in annotation.points:
                q_point = QPoint(point.x, point.y)
                polyline.append(q_point)

            result.append(polyline)

        return result

    @property
    def is_finished(self):
        return self._finished

    @is_finished.setter
    def is_finished(self, flag):
        self._finished = flag

Теперь для создания функции отмены мне нужно создать глубокую копию этого класса сцены, чтобы я мог сохранитьсостояние, которое было активным до изменения сцены.

Итак, в форме QDialog я пытаюсь сделать следующее:

class ImageAnnotator(QDialog):
    _state = None
    _previous_state = None

    def __init__(self, image):
        super().__init__()

        self._state = ImageAnnotatorState(image)
        self._previous_state = copy.deepcopy(self._state)

        self.show()

Здесь вызов Deepcopy завершается неудачей со следующим исключением:

SystemError: attempt to pickle unknown type 'QPolygonModel'

Что я делаю не так?

РЕДАКТИРОВАТЬ:

Воспроизводимый пример:

from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QDialog
from PyQt5.QtWidgets import QApplication

import copy
import sys

class Test(QtGui.QPolygon):
    idx = None

    def __init__(self, z = None):
        if z is None:
            super().__init__()
        else:
            super().__init__(z)


class State:
    test = None

    def __init__(self):
        self.test = [Test(), Test()]
        print(self.test)

class Main(QDialog):
    state = None
    prev = None

    def __init__(self):
        super().__init__()
        self.state = State()
        prev = copy.deepcopy(self.state)
        print(prev)

app = QApplication(sys.argv)
Main()

1 Ответ

2 голосов
/ 11 июня 2019

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

С другой стороны, если вы хотите установить значение по умолчанию в случае QPolygon, не используйте None, а пустой QPolygon.

С учетом вышеизложенного я реализовал следующее:

import copy
import random
from PyQt5 import QtCore, QtGui


class QPolygonModel(QtGui.QPolygon):
    def __init__(self, idx, polygon=QtGui.QPolygon()):
        super().__init__(polygon)
        self._idx = idx
        self._selected = False

    @property
    def idx(self):
        return self._idx

    @property
    def is_selected(self):
        return self._selected

    @is_selected.setter
    def is_selected(self, flag):
        self._selected = flag

    def get_points(self):
        res = []
        for i in range(0, self.size()):
            res.append(self.point(i))

        return res

    # https://stackoverflow.com/a/10622689
    def __deepcopy__(self, memo):
        o = QPolygonModel(self.idx)
        o.__dict__.update(self.__dict__)
        ba = QtCore.QByteArray()
        stream_w = QtCore.QDataStream(ba, QtCore.QIODevice.WriteOnly)
        stream_w << self
        stream_r = QtCore.QDataStream(ba, QtCore.QIODevice.ReadOnly)
        stream_r >> o
        return o


class State:
    def __init__(self):
        self.polylines = []
        for _ in range(4):
            poly = QtGui.QPolygon(
                [QtCore.QPoint(*random.sample(range(10), 2)) for _ in range(4)]
            )
            polyline = QPolygonModel(random.randint(0, 10), poly)
            self.polylines.append(polyline)


if __name__ == "__main__":
    curr = State()
    prev = copy.deepcopy(curr)

    assert len(curr.polylines) == len(prev.polylines)
    for polyline1, polyline2 in zip(curr.polylines, prev.polylines):
        assert id(polyline1) != id(polyline2)
        assert polyline1.size() == polyline2.size()
        assert polyline1.is_selected == polyline2.is_selected
        assert polyline1.idx == polyline2.idx
        for i, j in zip(range(polyline1.size()), range(polyline2.size())):
            assert polyline1.point(i) == polyline2.point(j)
...