A good many of the Qt classes overlap with Python classes. There is QString, which offers almost as much functionality as Python's string object and Unicode string object, QRegExp, which does the same as Python's re module. Not all Qt classes that overlap with Python classes are available from PyQt, but most are. The defining criterion is usually whether or not such an overlapping class is needed as an argument in method calls.
In those cases where there is duplication, it is up to you to decide which one to use. If you use as many Qt classes as possible, your application will appear less Pythonic, and will be more difficult to port to other Python gui's. However, it will also be easier to port your Python prototype to fast, compiled C++ code. Thus, it depends on whether you see your Python program as the final goal or as a prototype. It is best to take a firm stand, though — you shouldn't use Qt regular expressions and Python regular expressions in the same program.
For instance, the dirview Qt example program can use the QDir class, or use os.path.walk(). Compare the following setOpen functions. The first is a complex script which uses the Qt functions; the second uses setOpen with the Python equivalents. Both scripts create listview items for all entries in a directory:
Example 10-18. from dv_qt.py - using Qt utility classes
#!/usr/bin/env python import sys from qt import * class Directory(QListViewItem): def __init__(self, parent, name=None): apply(QListViewItem.__init__,(self,parent)) if isinstance(parent, QListView): self.p = None self.f = '/' else: self.p = parent self.f = name self.c = [] self.readable = 1 def setOpen(self, o): if o and not self.childCount(): s = self.fullName() thisDir = QDir(s) if not thisDir.isReadable(): self.readable = 0 return files = thisDir.entryInfoList() if files: for f in files: fileName = str(f.fileName()) if fileName == '.' or fileName == '..': continue elif f.isSymLink(): d = QListViewItem(self, fileName, 'Symbolic Link') elif f.isDir(): d = Directory(self, fileName) else: if f.isFile(): t = 'File' else: t = 'Special' d = QListViewItem(self, fileName, t) self.c.append(d) QListViewItem.setOpen(self, o) def setup(self): self.setExpandable(1) QListViewItem.setup(self) def fullName(self): if self.p: s = self.p.fullName() + self.f + '/' else: s = '/' return s def text(self, column): if column == 0: return self.f elif self.readable: return 'Directory' else: return 'Unreadable Directory' a = QApplication(sys.argv) mw = QListView() a.setMainWidget(mw) mw.setCaption('Directory Browser') mw.addColumn('Name') mw.addColumn('Type') mw.resize(400, 400) mw.setTreeStepSize(20) root = Directory(mw) root.setOpen(1) mw.show() a.exec_loop()
Example 10-19. fragment from db_python.py - using Python utility classes
... def setOpen(self, o): if o and not self.childCount(): s = self.fullName() if (not os.path.isdir(s)): self.readable == 0 return if (not os.access(s, os.F_OK or os.R_OK)): self.readable == 0 return files=os.listdir(s) if files: for fileName in files: f=os.path.join(s, fileName) if fileName == "." or fileName == "..": continue elif os.path.islink(f): d = QListViewItem(self, fileName, 'Symbolic Link') elif os.path.isdir(f): d = Directory(self, fileName) else: if os.path.isfile(f): t = 'File' else: print f t = 'Special' d = QListViewItem(self, fileName, t) self.c.append(d) QListViewItem.setOpen(self, o) ...
The result is the same:
The code is different, but the result the same.
The first snippet has been taken from the dirview.py example script that comes with PyQt and BlackAdder; the second is my own interpretation of the dirview.cpp example application. Both have been slightly adapted to make them more alike. Perhaps the similarities between the Qt QDir object and the Python os.path module are even more striking than the differences.
Qt offers a number of custom high-level data structures, where plain C++ is quite primitive in this respect. (Or, at least, C++ was quite primitive in this respect when Qt was first created. Recently, templates and a standard library have been added, and compilers are starting to support these constructs).
Python already has a dictionary, as well as list and string data structures that are powerful enough for most needs (what Python is missing is an ordered dictionary), so PyQt does not need the Qt high-level data structures, except where they are demanded as parameters in methods.
Qt has two basic high-level datastructures: arrays and collections. Collections are specialized into dictionaries, lists and maps. You can always use a Python list wherever a QList is needed, or a string where a QByteArray is needed. In other cases, some methods are not implemented because the datatype is not implemented, as is the case with QMap.
Table 10-1. Qt and Python high-level datastructures
Qt Class | Python Class | Implemented | Description |
---|---|---|---|
QArray | list | No | Array of simple types. |
QByteArray | String | No | Use a string wherever a method wants a QByteArray |
QPointArray | No equivalent | Yes | Array of QPoint objects — you can also store QPoint objects in a Python list, of course. |
QCollection | Dictionary | No | Abstract base class for QDict, QList and QMap. |
QDict | Dictionary | No | Just like Python dictionaries, but more complicated in use. |
QList | list | No | Just like Python lists, but more complicated |
QMap | Dictionary | No | Use a Python dictionary — however, when translating a Python prototype to C++, note that a QMap is based on values, not on references; the keys indexing the dictionary are copies of the original objects, not references. |
QCache | No equivalent | No | A QCache is a low-level class that caches string values so that two variables containing the same text don't use the memory twice. There are similar caches for integers and non-Unicode texts. Python performs the same trick; see the note: Python and Caching. |
QValueList | list | No | A low-level class that implements a list of values (instead of references to objects). |
QVariant | No equivalent | Partially | QVariant is a wrapper class that makes it possible to use C++ as if it were a loosely-typed language (which Python already is). This class is used for implementing class properties (I find it to be a monstrosity compared to Visual Basic's Variant type). |
Python and caching: Python caches certain often-used values and shares those values across variables. Numbers from 0 to 99 (inclusive) are cached, and strings are always cached. Qt uses the same trick from strings and some other objects
Python 2.2a4 (#1, Oct 4 2001, 15:35:57) [GCC 2.95.2 19991024 (release)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> a=1 >>> b=1 >>> id(a) 135268920 >>> id(b) 135268920 >>> a=100 >>> b=100 >>> id(a) 135338528 >>> id(b) 135338504 >>> a="bla" >>> b="bla" >>> id(a) 135563808 >>> id(b) 1355638
Qt's file classes include the following:
QDir — directory information
QFile — file handling
QFileInfo — file information
QIODevice — abstract IO device
QBuffer — helper class for buffered IO
QTextStream — abstract class for text IO
QTextIStream — text input IO
QTextOSStream — text output IO
QAsyncIO — abstract base for asynchronous IO
QDataSink — asynchronous data consumer
QDataSource — asynchronous data produced of data
QDataStream — binary IO
QIODeviceSource — a datasource that draws data from a QIODevice, like QFile.
Qt's file and IO handling classes are divided into three groups: classes to handle files on a file system, classes that work on streams, and asynchronous IO classes. We have already seen an example of working with QDir. QFile is Qt's equivalent of the Python file object; it is almost fully implemented in PyQt, with some changes due to naming conflicts. QFileInfo is a useful class that encapsulates and caches information including name, path, creation date and access rights. You could use the various function in Python's os.path module, but those don't cache information.
The base class for all these IO-oriented classes is QIODevice, which is completely implemented in PyQt. You can subclass it for your own IO classes. Qt divides its stream handling into text streams and binary streams. Only the text stream handling in implemented in PyQt, with the QTextStream, QTextIStream and QTextOStream classes. QTextStream is the base class, QTextIStream provides input stream functionality and QTextOStream output stream functionality. One problem remains with these stream classes — operator overloading. C++ relies on the >> and << operators to read from and write to a stream. PyQt doesn't support this yet, so you cannot actually make use of the streaming capabilities. Instead, you will have to limit yourself to using read() to read the entire contents of the stream.
The asynchronous IO classes, the buffer class and the binary IO classes have not been implemented yet. You can easily substitute the various Python modules for file and IO handling. You can use asyncore in place QAsyncIO. The Python file object is buffered by nature, if you open() your files with the bufsize parameter set.
QDate — representation of a data
QTime — clock and time functions
QDateTime — combination of QDate and QTime
While Python has a time module, this only presents a low-level interface to the system date and time functions (and the sleep() function, which halts processing for a while). It does not provide a high-level encapsulation of dates and times. PyQt, however, provides just that with QDate and QTime. QDate is especially suited to data arithmetic, and can hold dates from 1752 to 8000. The first limit is based on the date that our Gregorian calendar was introduced; if you need the Julian calendar (or perhaps Vikram Samvat or other exotic calendars), you must write your own Date class.
QMimeSource — a piece of formatted data
QMimeSourceFactory — a provider of formatted data
Python mimetools and the MimeWriter modules are not exactly equivalent to the PyQt QMimeSource and QMimeSourceFactory classes. The Python modules are optimized for the handling of mime-encoded e-mail messages. The PyQt classes are a more generalized abstraction of formatted data, where the format is identified by the IANA list of MIME media types. QMimeSource is used extensively in the drag'n'drop subsystem, in the clipboard handling, and in the production of images for rich text widgets.
An example is given in the application.py script that is included with PyQt. Below, I show a relevant fragment of that example, which takes a bit of html-formatted text with an <img> tag to a certain name, which is resolved using QMimeSourceFactory:
Example 10-20. Using QMimeSourceFactory (application.py)
... fileOpenText = \ '''<img source="fileopen"> Click this button to open a <em>new file</em>.<br><br> You can also select the <b>Open</b> command from the <b>File</b> menu.''' ... QWhatsThis.add(self.fileOpen,fileOpenText) QMimeSourceFactory.defaultFactory().setPixmap('fileopen',openIcon) ...
QString — string handling
QRegExp — regular expressions that work on a string.
QChar — one Unicode character
QValidator — validates input text according to certain rules
QTextCodec — conversions between text encodings
QTextDecoder — decode a text to Unicode
QTextEncoder — encode a text from Unicode
Qt's string handling really excels: it is thoroughly based upon Unicode, but provides easy functionality for other character encodings. However, plain Python also provides Unicode string functionality, and the interplay between Python strings and PyQt QStrings can be quite complex.
For most purposes, however, the conversions are transparent, and you can use Python strings as parameters in any function call where a QString is expected. If you run across more complex problems, you can consult Chapter 8, on String Objects in Python and Qt.
QMutex
Python threads and Qt threads bite each other frequently. Qt thread support itself is still experimental, and with Unix/X11, most people still use the un-threaded Qt library. The C++ Qt thread class QMutex has not been ported to PyQt, so you cannot serialize access to gui features.
Python thread support is far more mature, but doesn't mix too well with PyQt — you don't want two threads accessing the same gui element. You're quite safe though, as long as your threads don't access the gui. The next example shows a simple pure-python script with two threads:
Example 10-21. thread1.py — Python threads without gui
# # thread1.py # import sys import time from threading import * class TextThread(Thread): def __init__(self, name, *args): self.counter=0 self.name=name apply(Thread.__init__, (self, ) + args) def run(self): while self.counter < 200: print self.name, self.counter self.counter = self.counter + 1 time.sleep(1) def main(args): thread1=TextThread("thread1") thread2=TextThread("thread2") thread1.start() thread2.start() if __name__=="__main__": main(sys.argv)
The next example has a Qt window. The threads run quite apart from the window, and yet everything operates fine— that is, until you try to close the application. The threads will continue to run until they are finished, but it would be better to kill the threads when the last window is closed. Killing or stopping threads from outside the threads is not supported in Python, but you can create a global variable, stop, to circumvent this. In the threads themselves, you then check whether stop is true.
Example 10-22. Python threads and a PyQt gui window
# # thread2.py - Python threads # import sys, time from threading import * from qt import * class TextThread(Thread): def __init__(self, name, *args): self.counter=0 self.name=name apply(Thread.__init__, (self, ) + args) def run(self): while self.counter < 200: print self.name, self.counter self.counter = self.counter + 1 time.sleep(1) class MainWindow(QMainWindow): def __init__(self, *args): apply(QMainWindow.__init__, (self,) + args) self.editor=QMultiLineEdit(self) self.setCentralWidget(self.editor) self.thread1=TextThread("thread1") self.thread2=TextThread("thread2") self.thread1.start() self.thread2.start() def main(args): app=QApplication(args) win=MainWindow() win.show() app.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()")) app.exec_loop() if __name__=="__main__": main(sys.argv)
Another technique (though dangerous!) is to have the GUI use a timer to periodically check the variables produced by the threads. However, concurrent access of a variable can lead to nasty problems.
The Qt URL-handling classes are quite equivalent to Python's urllib module. It's up to you to choose which you prefer.
QUrl
QUrlInfo
QUrlOperator
In addition, there are two Qt modules that are also available completely as Python modules: the XML module (wrapped by Jim Bublitz) and the Network module. The Python equivalent of Qt's XML module, for simple tasks, xmllib, and for more complicated problems xml.sax. While xmllib is deprecated, it is still very useful and one the simplest ways of handling XML data. The xml.sax module depends on the presence of the expat parser, which is not available on every system.
The Qt network module has not been completely wrapped in PyQt yet. It is mainly useful if you want to program your own network clients and servers, and intend to move your code to C++ one day. Python offers equivalent modules for every service, and a lot more, too (such as http and gopher clients). You might find Qt's socket objects a bit more convenient than Python's offerings. Another possible reason to use the Qt socket classes is that they are better implemented on Windows than the Python socket classes.
Table 10-2. Qt and Python network classes
Qt Class | Python Class | Implemented | Description |
---|---|---|---|
QSocket | socket | Partially implemented | Low level network connection object. Note that Python's socket module is useful both for clients and servers, while Qt's QSocket is for clients only: servers use QServerSocket. |
QSocketDevice | socket | No | This class is mostly intended for use inside Qt - use QIODevice instead, which is completely implemented in PyQt. |
QServerSocket | socket, SocketServer | Partially implemented | The server-side complement of QSocket. Again, Python's socket module servers both for server and client applications. Python offers the server-specific SocketServer module. |
QHostAddress | No real equivalent | Partially — not the functionality for IPv6. | A platform and protocol independent representation of an IP address. |
QDns | No real equivalent | Not implemented | Asynchronous DNS lookups — it's not implemented, all Python libraries do automatic synchronous DNS lookups, as do the QSocket derived Qt classes. |
QFtp | ftplib | Not implemented | This class is seldom used: it's easier to just open an URL either with QUrlOperator or one of the Python Internet protocol handling libraries like urllib. |