Pickle QPolygon в Python 3.6+ и PyQt5 - PullRequest
       16

Pickle QPolygon в Python 3.6+ и PyQt5

0 голосов
/ 28 февраля 2019

Я пытаюсь засечь QPolygon и загрузить его потом, но получаю ошибку.Я сделал это на Python2 с PyQt4, но хочу использовать его сейчас на Python3 с PyQt5.

Я не хочу читать / загружать данные, сгенерированные с помощью Python 2!Файл pickle просто используется для временного хранения Qt-элементов, таких как QPolygons, из Python3 в Python3.

Я протестировал различные варианты протокола от 1-4 для pickle.dump () и попытался использовать "fix_imports = True"опция, которая не должна иметь значения в Python3.

Вот мой упрощенный код

from PyQt5.QtGui import QPolygon
from PyQt5.QtCore import QPoint
import pickle

file_name = "test_pickle.chip"

with open(file_name, 'wb') as f:
    poly = QPolygon((QPoint(1, 1), QPoint(2, 2))) 
    pickle.dump(poly, f, protocol=2)  # , fix_imports=True)

# loading the data again
with open(file_name, 'rb') as f:
    elem = pickle.load(f, encoding='bytes')  # , fix_imports=True)

Я получаю следующее сообщение об ошибке, но ничего не могу с ним сделать:

elem = pickle.load(f, encoding='bytes')  # , fix_imports=True)
TypeError: index 0 has type 'int' but 'QPoint' is expected

Есть ли альтернатива маринованию?Заранее спасибо!

Ответы [ 4 ]

0 голосов
/ 01 марта 2019

Это должно быть ошибкой в ​​pyqt5, так как документация гласит , что поддерживается травление QPolygon.Поэтому, пожалуйста, отправьте сообщение об ошибке в список рассылки pyqt на вашем примере.

Пока что самой простой альтернативой является использование QSettings :

from PyQt5.QtGui import QPolygon
from PyQt5.QtCore import QSettings, QPoint

file_name = "test_pickle.chip"

poly = QPolygon((QPoint(1, 1), QPoint(2, 2)))

# write object
s = QSettings(file_name, QSettings.IniFormat)
s.setValue('poly', poly)

del s, poly

# read object
s = QSettings(file_name, QSettings.IniFormat)
poly = s.value('poly')

print(poly)
print(poly.point(0), poly.point(1))

Вывод:

<PyQt5.QtGui.QPolygon object at 0x7f871f1f8828>
PyQt5.QtCore.QPoint(1, 1) PyQt5.QtCore.QPoint(2, 2)

Это можно использовать с любыми типами, поддерживаемыми PyQt для засолки, плюс все, что PyQt может преобразовать в / из QVariant .PyQt также поддерживает аргумент type для QSettings.value(), который можно использовать для явного указания требуемого типа.А поскольку QSettings предназначен для хранения данных конфигурации приложения, любое количество объектов может храниться в одном и том же файле (что-то вроде модуля полки python ).

0 голосов
/ 28 февраля 2019

Немного поиска в документах расскажет вам, как вы можете реализовать выборочную выборку для пользовательских классов с помощью __reduce__ метода .По сути, вы возвращаете кортеж, включающий конструктор для нового объекта, который будет создан при снятии выборки, а также кортеж аргументов, которые будут переданы конструктору.Затем вы можете затем передать объект state (см. __getstate__ и __setstate__) и некоторые итераторы для добавления позиционных данных и данных значения ключа.

путем создания подкласса QPolygon мы можем добавить возможность выбора какнапример: (возможно, это можно было бы очистить / реструктурировать, но это первая реализованная мною реализация)

from PyQt5.QtGui import QPolygon
from PyQt5.QtCore import QPoint
import pickle

class Picklable_QPolygon(QPolygon):

    def __reduce__(self):
        # constructor, (initargs), state object passed to __setstate__
        return type(self), (), self.__getstate__()

    #I'm not sure where this gets called other than manually in __reduce__
    #  but it seemed like the semantically correct way to do it...
    def __getstate__(self):
        state = [0]
        for point in self:
            state.append(point.x())
            state.append(point.y())
        return state

    #putPoints seems to omit the `nPoints` arg compared to c++ version.
    #  this may be a version dependent thing for PyQt. Definitely test that
    #  what you're getting out is the same as what you put in..
    def __setstate__(self, state):
        self.putPoints(*state)

poly = Picklable_QPolygon((QPoint(1, 1), QPoint(2, 2)))
s = pickle.dumps(poly)

elem = pickle.loads(s)
0 голосов
/ 01 марта 2019

Вдохновленный предыдущими ответами, я создал функцию, которая принимает некий Qt, который поддерживает QDataSteam и возвращает класс, который наследуется от этого класса и может быть выбран, в следующем примере, который я показываю с QPoygon и QPainterPath:

import pickle
from PyQt5 import QtCore, QtGui

def picklable_reduce(self):
    return type(self), (), self.__getstate__()

def picklable_getstate(self):
    ba = QtCore.QByteArray()
    stream = QtCore.QDataStream(ba, QtCore.QIODevice.WriteOnly)
    stream << self
    return ba

def picklable_setstate(self, ba):
    stream = QtCore.QDataStream(ba, QtCore.QIODevice.ReadOnly)
    stream >> self

def create_qt_picklable(t):
    return type("Picklable_{}".format(t.__name__), (t,),
        {
            '__reduce__': picklable_reduce,
            '__getstate__': picklable_getstate,
            '__setstate__': picklable_setstate
        }
    ) 

if __name__ == '__main__':
    # QPolygon picklable
    Picklable_QPolygon = create_qt_picklable(QtGui.QPolygon)
    old_poly = Picklable_QPolygon((QtCore.QPoint(1, 1), QtCore.QPoint(2, 2)))
    s = pickle.dumps(old_poly)
    new_poly = pickle.loads(s)
    assert(old_poly == new_poly)
    # QPainterPath picklable
    Picklable_QPainterPath = create_qt_picklable(QtGui.QPainterPath)
    old_painterpath = Picklable_QPainterPath()
    old_painterpath.addRect(20, 20, 60, 60)
    old_painterpath.moveTo(0, 0)
    old_painterpath.cubicTo(99, 0,  50, 50,  99, 99)
    old_painterpath.cubicTo(0, 99,  50, 50,  0, 0);
    s = pickle.dumps(old_painterpath)
    new_painterpath= pickle.loads(s)
    assert(old_painterpath == new_painterpath)

Использование кода операции:

if __name__ == '__main__':
    Picklable_QPolygon = create_qt_picklable(QtGui.QPolygon)

    file_name = "test_pickle.chip"
    poly = Picklable_QPolygon((QtCore.QPoint(1, 1), QtCore.QPoint(2, 2))) 
    with open(file_name, 'wb') as f:
        pickle.dump(poly, f, protocol=2)  # , fix_imports=True)

    elem = None
    with open(file_name, 'rb') as f:
        elem = pickle.load(f, encoding='bytes')  # , fix_imports=True)

    assert(poly == elem)
0 голосов
/ 28 февраля 2019

Вы можете использовать QDataStream для сериализации / десериализации объектов Qt:

from PyQt5 import QtCore
from PyQt5.QtGui import QPolygon
from PyQt5.QtCore import QPoint, QFile, QIODevice, QDataStream, QVariant

file_name = "test.dat"

output_file = QFile(file_name)
output_file.open(QIODevice.WriteOnly)
stream_out = QDataStream(output_file)
output_poly = QPolygon((QPoint(1, 6), QPoint(2, 6)))
output_str = QVariant('foo')  # Use QVariant for QString
stream_out << output_poly << output_str
output_file.close()

input_file = QFile(file_name)
input_file.open(QIODevice.ReadOnly)
stream_in = QDataStream(input_file)
input_poly = QPolygon()
input_str = QVariant()
stream_in >> input_poly >> input_str
input_file.close()

print(str(output_str.value()))
print(str(input_str.value()))
...