52
to support enhancements to existing data types. This is not a problem because every
version of QDataStream can read data stored in the formats used by all its previous
versions. And in addition, QDataStream always stores integers the same way, no matter
which version of QDataStream is being used.
def saveQDataStream(self):
error = None
fh = None
try:
fh = QFile(self.__fname)
if not fh.open(QIODevice.WriteOnly):
raise IOError, unicode(fh.errorString())
stream = QDataStream(fh)
stream.writeInt32(MovieContainer.MAGIC_NUMBER)
stream.writeInt32(MovieContainer.FILE_VERSION)
stream.setVersion(QDataStream.Qt_4_2)
Since PyQt uses return values rather than exceptions, if the file cannot be opened we raise
an exception ourselves since we prefer the exception-based approach to error handling.
Having opened the file, we create a QDataStream object to write to it.
PyQt cannot guess what size integer we want to use to store int and long integers, so we
must write integer values using the writeIntn() and writeUIntn() methods, where
n is 8, 16, 32, or 64, i.e., the number of bits to use to store the integer. For floating-point
numbers, QDataStream provides the writeDouble() and readDouble() methods.
These operate on Python floats (equivalent to C and C++ doubles), and are stored as 64-
bit values in IEEE-754 format.
The first integer we write is the "magic number". This is an arbitrary number that we use
to identify My Movies data files. This number will never change. We should give any custom
binary data file a unique magic number, since filename extensions cannot always be relied
upon to correctly identify a file's type. Next we write a "file version". This is the version of
our file format (we have set it to be 100). If later on, we decide to change the file format,
the magic number will remain the same—after all the file will still hold movie data—but
the file format will change (e.g., to 101) so that we can execute different code to load it to
account for the difference in format.
Since integers are always saved in the same format, we can safely write them before setting
the QDataStream version. But once we have written the magic number and file version,
we should set the QDataStream version to the one that PyQt should use for writing and
reading the rest of the data. If we want to take advantage of a later version we could use
our original file format for version Qt_4_2, and another file format for the later version.
Then, when we come to load the data, we could set the QDataStream version depending
on our file format number.
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Page 251
Return to Table of
Contents
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
By Mark Summerfield ISBN: 9780132354189 Publisher: Prentice Hall
Prepared for Paul Waddell, Safari ID: pwaddell@u.washington.edu
Print Publication Date: 2007/10/19
User number: 905221 Copyright 2007, Safari Books Online, LLC.
This PDF is exclusively for your use in accordance with the Safari Terms of Service. No part of it may be reproduced or transmitted in any form by any means without the prior
written permission for reprints and excerpts from the publisher. Redistribution or other use that violates the fair use priviledge under U.S. copyright laws (see 17 USC107) or that
otherwise violates the Safari Terms of Service is strictly prohibited.
48
Setting the QDataStream version is very important, since it will ensure that any PyQt data
type is saved and loaded correctly. The only situation where it does not matter is if we are
only saving and loading integers, since their representation never changes.
for key, movie in self.__movies:
stream << movie.title
stream.writeInt16(movie.year)
stream.writeInt16(movie.minutes)
stream << movie.acquired << movie.notes
Now we iterate over the movie data, writing each movie's data to the data stream. The
QDataStream class overloads the << operator for many PyQt classes, including for
example, QString, QDate, and QImage, so we must use a C++-like streaming syntax to
write our data. The << operator writes its right operand to the data stream that is its left
operand. It can be applied repeatedly to the same stream, since it returns the stream it is
applied to, but for integers, we must use the writeIntn() and writeUIntn() methods.
Figure 8.3. The QDataStream My Movies File Format
Since we are writing binary data, we do not have to do any formatting. We just have to
ensure that when we load the data back, we use the same QDataStream version, and that
we load in the same data types in the same order as we saved. So, in this case, we will load
back two integers (the magic and file version numbers), then any number of movie records
each comprising a string, two integers, a date, and a string.
except (IOError, OSError), e:
error = "Failed to save: %s" % e
finally:
if fh is not None:
fh.close()
if error is not None:
return False, error
self.__dirty = False
return True, "Saved %d movie records to %s" % (
len(self.__movies),
QFileInfo(self.__fname).fileName())
If there are any errors, we simply give up and return a failure flag and an error message.
Otherwise we clear the dirty flag and return a success flag and a message indicating how
many records were saved.
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Page 252
Return to Table
of
Contents
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
By Mark Summerfield ISBN: 9780132354189 Publisher: Prentice Hall
Prepared for Paul Waddell, Safari ID: pwaddell@u.washington.edu
Print Publication Date: 2007/10/19
User number: 905221 Copyright 2007, Safari Books Online, LLC.
This PDF is exclusively for your use in accordance with the Safari Terms of Service. No part of it may be reproduced or transmitted in any form by any means without the prior
written permission for reprints and excerpts from the publisher. Redistribution or other use that violates the fair use priviledge under U.S. copyright laws (see 17 USC107) or that
otherwise violates the Safari Terms of Service is strictly prohibited.
53
The corresponding load method is just as straightforward, although it does have to do more
error handling.
def loadQDataStream(self):
error = None
fh = None
try:
fh = QFile(self.__fname)
if not fh.open(QIODevice.ReadOnly):
raise IOError, unicode(fh.errorString())
stream = QDataStream(fh)
magic = stream.readInt32()
if magic != MovieContainer.MAGIC_NUMBER:
raise IOError, "unrecognized file type"
version = stream.readInt32()
if version < MovieContainer.FILE_VERSION:
raise IOError, "old and unreadable file format"
elif version > MovieContainer.FILE_VERSION:
raise IOError, "new and unreadable file format"
stream.setVersion(QDataStream.Qt_4_2)
self.clear(False)
We create the QFile object and QDataStream object the same as before, except this time
using ReadOnly rather than WriteOnly mode. Then we read in the magic number. If
this is not the unique My Movies data file number, we raise an exception. Next we read the
file version, and make sure it is one that we can handle. It is at this point that we would
branch depending on the file version, if we had more than one version of this file format
in use. Then we set the QDataStream version.
The next step is to clear the movies data structures. We do this as late as possible so that
if an exception was raised earlier, the original data would be left intact. The False
argument tells the clear() method to clear __movies and __movieFromId, but not
the filename.
while not stream.atEnd():
title = QString()
acquired = QDate()
notes = QString()
stream >> title
year = stream.readInt16()
minutes = stream.readInt16()
stream >> acquired >> notes
self.add(Movie(title, year, minutes, acquired, notes))
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Page 253
Return to Table
of
Contents
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
By Mark Summerfield ISBN: 9780132354189 Publisher: Prentice Hall
Prepared for Paul Waddell, Safari ID: pwaddell@u.washington.edu
Print Publication Date: 2007/10/19
User number: 905221 Copyright 2007, Safari Books Online, LLC.
This PDF is exclusively for your use in accordance with the Safari Terms of Service. No part of it may be reproduced or transmitted in any form by any means without the prior
written permission for reprints and excerpts from the publisher. Redistribution or other use that violates the fair use priviledge under U.S. copyright laws (see 17 USC107) or that
otherwise violates the Safari Terms of Service is strictly prohibited.
62
Approaches to File Error Handling
The approach used for handling file errors in this chapter has the structure
shown below on the left. Another equally valid approach, used for example, in
chap09/textedit.py and chap14/ships.py, is shown below on the right.
error = None
fh = None
try:
# open file and read data
except (IOError, OSError), e:
error = unicode(e)
finally:
if fh is not None:
fh.close()
if error is not None:
return False, error
return True, "Success"
exception = None
fh = None
try:
# open file and read data
except (IOError, OSError), e:
exception = e
finally:
if fh is not None:
fh.close()
if exception is not None:
raise exception
At the call point, and assuming we are dealing with a load() method, we might
use code like this for the left-hand approach:
ok, msg = load(args)
if not ok:
QMessageBox.warning(self, "File Error", msg)
And for the right-hand approach we could use code like this:
try:
load(args)
except (IOError, OSError), e:
QMessageBox.warning(self, "File Error", unicode(e))
Another approach, used for example in chap09/sditexteditor.pyw and
chap12/pagedesigner.pyw, is to do all the error handling inside the file
handling method itself:
fh = None
try:
# open file and read data
except (IOError, OSError), e:
QMessageBox.warning(self, "File Error", unicode(e))
finally:
if fh is not None:
fh.close()
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Page 254
Return to Table
of
Contents
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
By Mark Summerfield ISBN: 9780132354189 Publisher: Prentice Hall
Prepared for Paul Waddell, Safari ID: pwaddell@u.washington.edu
Print Publication Date: 2007/10/19
User number: 905221 Copyright 2007, Safari Books Online, LLC.
This PDF is exclusively for your use in accordance with the Safari Terms of Service. No part of it may be reproduced or transmitted in any form by any means without the prior
written permission for reprints and excerpts from the publisher. Redistribution or other use that violates the fair use priviledge under U.S. copyright laws (see 17 USC107) or that
otherwise violates the Safari Terms of Service is strictly prohibited.
60
At the call point we simply call load(args), leaving the load() method itself
to report any problems to the user.
Table 8.1. Selected QDataStream Methods
Syntax
Description
s.atEnd()
Returns True if the end of QDataStream s has been reached
s.setVersion(v)
Sets QDataStream s's version to v, where v is one of Qt_1_0, ..., Qt_4_2, Qt_4_3
s << x
Writes object x to QDataStream s; x can be of type QBrush, QColor, QDate, QDateTime,
QFont, QIcon, QImage, QMatrix, QPainterPath, QPen, QPixmap, QSize, QString,
QVariant etc.
s.readBool()
Reads a bool from QDataStream s
s.readDouble()
Reads a float from QDataStream s
s.readInt16()
Reads a 16-bit int from QDataStream s. There is also readUInt16()
s.readInt32()
Reads a 32-bit int from QDataStream s. There is also readUInt32()
s.readInt64()
Reads a 64-bit long from QDataStream s. There is also a readUInt64() method
x = QString() s >>
x
Reads object x from QDataStream s; x must already exist (so that the data stream knows what
data type to read), and can be any of the types writeable by <<
s.writeBool(b)
Writes bool b to QDataStream s
s.writeDouble(f)
Writes float f to QDataStream s
s.writeInt16(i)
Writes int i to QDataStream s. There is also writeUInt16()
s.writeInt32(i)
Writes int i to QDataStream s. There is also writeUInt32()
s.writeInt64(l)
Writes long l to QDataStream s. There is also writeUInt64()
We could have stored the number of movies at the beginning of the file, after the file
version. But instead we simply iterate over the data stream until we reach the end. For
non-numeric data types we must create variables that hold empty values of the correct
type. Then we use the >> operator which takes a data stream as its left operand and a
variable as its right operand; it reads a value of the right operand's type from the stream
and puts it into the right operand. The operator returns the file stream, so it can be applied
repeatedly.
For integers we must always read using the readIntn() and readUIntn() methods
with the same number of bits as we specified when writing.
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Page 255
Return to Table
of
Contents
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
By Mark Summerfield ISBN: 9780132354189 Publisher: Prentice Hall
Prepared for Paul Waddell, Safari ID: pwaddell@u.washington.edu
Print Publication Date: 2007/10/19
User number: 905221 Copyright 2007, Safari Books Online, LLC.
This PDF is exclusively for your use in accordance with the Safari Terms of Service. No part of it may be reproduced or transmitted in any form by any means without the prior
written permission for reprints and excerpts from the publisher. Redistribution or other use that violates the fair use priviledge under U.S. copyright laws (see 17 USC107) or that
otherwise violates the Safari Terms of Service is strictly prohibited.
53
Once we have read in a single movie's data, we create a new Movie object and immediately
add it to the container's data structures using the add() method we reviewed in the
previous section.
except (IOError, OSError), e:
error = "Failed to load: %s" % e
finally:
if fh is not None:
fh.close()
if error is not None:
return False, error
self.__dirty = False
return True, "Loaded %d movie records from %s" % (
len(self.__movies),
QFileInfo(self.__fname).fileName())
The error handling, and the final return statement is structurally the same as we used
for the save method.
Using the PyQt QDataStream class to write binary data is not very different in principle
from using Python's file class. We must be careful to use the correct QDataStream
version, and ought to use a magic number and file version, or some equivalent approach.
The use of the << and >> operators is not very Pythonic, but it is easy to understand.
We could have put code for writing a movie in the Movie class itself, perhaps with a method
that took a QDataStream argument and wrote the movie's data to it. In practice it is
usually more convenient, and almost always more flexible, to have the data container do
the file handling rather than the individual data items.
Writing and Reading Using the pickle Module
Python's standard pickle module, and its faster cPickle counterpart, can save arbitrary
Python data structures to disk and load them back again. These modules provide exactly
the same functions and functionality as each other. The only difference between them is
that the pickle module is implemented purely in Python, and the cPickle module is
implemented in C. These modules only understand the data types in the Python standard
library, and classes that are built from them. If we want to pickle PyQt-specific data types
with PyQt versions prior to PyQt 4.3, we must tell the pickle (or cPickle) module how
to handle them.
def _pickleQDate(date):
return QDate, (date.year(), date.month(), date.day())
def _pickleQString(qstr):
return QString, (unicode(qstr),)
copy_reg.pickle(QDate, _pickleQDate)
copy_reg.pickle(QString, _pickleQString)
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Page 256
Return to Table
of
Contents
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
Rapid GUI Programming with Python and Qt: The Definitive Guide to PyQt Programming
By Mark Summerfield ISBN: 9780132354189 Publisher: Prentice Hall
Prepared for Paul Waddell, Safari ID: pwaddell@u.washington.edu
Print Publication Date: 2007/10/19
User number: 905221 Copyright 2007, Safari Books Online, LLC.
This PDF is exclusively for your use in accordance with the Safari Terms of Service. No part of it may be reproduced or transmitted in any form by any means without the prior
written permission for reprints and excerpts from the publisher. Redistribution or other use that violates the fair use priviledge under U.S. copyright laws (see 17 USC107) or that
otherwise violates the Safari Terms of Service is strictly prohibited.
Documents you may be interested
Documents you may be interested