3
votes

Pickle un QPolygon en python 3.6+ et PyQt5

J'essaye de décaper un QPolygon et de le charger ensuite, mais j'obtiens une erreur. J'ai fait cela sur Python2 avec PyQt4 mais je veux l'utiliser maintenant sur Python3 avec PyQt5.

Je ne veux pas lire / charger les données générées avec Python 2! Le fichier pickle est simplement utilisé pour stocker temporairement des éléments Qt tels que QPolygons de Python3 à Python3.

J'ai testé différentes options de protocole de 1 à 4 pour pickle.dump () et essayé d'utiliser le "fix_imports = True "option qui ne devrait pas faire de différence dans Python3.

Voici mon code simplifié

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

Je reçois le message d'erreur suivant mais je ne peux pas le faire n'importe quoi avec:

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)

Y a-t-il une alternative au cornichon? Merci d'avance!


0 commentaires

4 Réponses :


2
votes

Vous pouvez utiliser QDataStream pour sérialiser / désérialiser les objets 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()))


4 commentaires

Il semble qu'il y ait un bug concernant l'utilisation de pickle avec QPolygon. J'aime votre réponse car il semble que c'est un moyen rapide et facile de remplacer pickle par QDataStream. Mais j'ai peut-être un peu trop simplifié mon exemple. J'ai également besoin de stocker par ex. chaînes à l'intérieur du fichier / flux de sortie. Est-ce également possible avec QDataStream? Mon premier test n'a pas réussi à stocker des chaînes et plusieurs QPolygon dans un fichier de sortie.


Salut, oui, vous pouvez stocker tout ce que vous voulez, mais vous devez utiliser des objets Qt, je vais mettre à jour mon exemple pour vous montrer.


Existe-t-il une possibilité de vérifier le type de l'élément suivant lors de la lecture du QDataStream? Je dois vérifier si une chaîne ou un QPolygon est le suivant. Pour déterminer le type, je dois lire les données dans un ex. QVariant mais cela détruit en quelque sorte QPolygons.


Je ne pense pas que ce soit possible par défaut, mais vous devriez connaître l’ordre dans lequel vous avez ajouté les éléments, non? Vous pouvez peut-être créer une classe qui hérite de QObject et qui contient une liste de types, puis passer cette classe en premier et l'utiliser pour savoir de quel type d'éléments vous aurez besoin lors de la désérialisation.



1
votes

Un peu de recherche dans la documentation vous indique comment implémenter le décapage personnalisé pour les classes personnalisées via __reduce__ méthode . Fondamentalement, vous retournez un tuple comprenant le constructeur du nouvel objet à créer lors du décapage ainsi qu'un tuple d'arguments qui sera passé au constructeur. Si vous le souhaitez, vous pouvez ensuite passer un objet state (voir __getstate__ et __setstate__ ) et certains itérateurs pour ajouter des données de position et des données de valeur-clé. en sous-classant QPolygon , nous pouvons ajouter la capacité de décapage en tant que telle: (cela pourrait probablement être nettoyé / restructuré, mais c'est la première implémentation à laquelle j'ai travaillé)

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 commentaires

0
votes

Inspiré par les réponses précédentes, j'ai créé une fonction qui prend une sorte de Qt qui prend en charge QDataSteam et retourne une classe qui hérite de cette classe et est picklable, dans l'exemple suivant, je montre avec QPoygon et QPainterPath:

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 commentaires

1
votes

Cela doit être un bogue dans pyqt5, car la documentation indique que le pickling QPolygon est pris en charge. Veuillez donc publier un rapport de bogue sur la liste de diffusion pyqt en utilisant votre exemple.

Dans en attendant, l'alternative de loin la plus simple est d'utiliser QSettings : p >

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

Résultat:

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

Ceci peut être utilisé avec tout type de support PyQt pour le pickling, plus tout ce que PyQt peut convertir vers / depuis un QVariant . PyQt prend également en charge un argument type pour QSettings.value () qui peut être utilisé pour indiquer explicitement le type requis. Et comme QSettings est conçu pour stocker les données de configuration d'application, n'importe quel nombre d'objets peut être stocké dans le même fichier (un peu comme celui de python module shelve ).


7 commentaires

J'ai également trouvé les informations dans la documentation, mais je ne savais pas si je faisais quelque chose de mal. J'ai écrit à la liste de diffusion pyqt et je vous tiendrai au courant.


@HoWil OK. Y a-t-il une raison pour laquelle vous ne considérez pas QSettings comme une bonne alternative? Il semble beaucoup plus simple à utiliser que toute autre solution (y compris pickle lui-même). Sous les couvertures, QSettings utilise les opérateurs de flux de données pour faire la sérialisation, donc il semblerait que cela doit être assez efficace (puisque tout se passe en c ++).


La raison en est que je dois nommer chaque entrée que je mets dans les QSettings. Dans l'exemple simplifié que j'ai utilisé pour montrer le problème, il n'y a qu'un seul QPolygon. En réalité, il existe des centaines de polygones et aussi des chaînes. Ce qui rend un peu difficile la gestion de tous les noms de variables. J'ai finalement accepté la solution d'isma en utilisant QDataStream qui est le plus équivalent à pickle en l'écrivant simplement l'un après l'autre et en le lisant de la même manière. Néanmoins, merci pour l'aide et la discussion.


un inconvénient de cette méthode est que les données sont toujours stockées sous forme de fichier de configuration / paramètres dans votre dossier de configuration Qt. Sous Linux, il s'agit de .config dans votre dossier personnel mais pas à côté de mon fichier python ou dans le chemin que j'ai spécifié.


@HoWil Ce n'est pas vrai - vous pouvez spécifier un chemin particulier . Je mettrai à jour ma réponse pour utiliser cette méthode (c'est en fait ce que j'avais initialement prévu). Cela utilisera le format de fichier ini standard.


Je vous remercie. L'inconvénient est que les fichiers exportés sont plusieurs fois plus volumineux par rapport à l'exportation avec pickle (binaire). Enfin, j'utilise cette solution pour mon projet actuel même si ce n'est pas une réponse à 100% à ma question initiale.


@HoWil Juste au cas où vous l'auriez manqué, le bogue a maintenant été corrigé < / a>, il devrait donc apparaître dans la prochaine version de pyqt5 (5.12.1).