SaltyCrane Blog — Notes on JavaScript and web development

PyQt4 QItemDelegate example with QListView and QAbstractListModel

I am currently working on a mini project which uses a QListView to display items in a list box. I am happy with most of the default behavior in the list view, however, I want to change how the highlighting of selected items is displayed. Currently, in my Windows environment, selecting an item in the list highlights the item in dark blue. This is fine, however, when the list box loses focus, the highlight color turns to a light gray, which is hard for me to see. I would like the selection to have a red highlight, whether the widget has focus or not.

My solution is to add a custom delegate to my list view. Normally, a standard view uses a default delegate (QItemDelegate) to render and edit the model's data. To customize the way the data is displayed in the view, I subclass QItemDelegate and implement a custom paint() method to set the background color to red for selected items. (Note, it is possible to specify certain formatting (including background color) using ItemDataRoles in the QAbstractListModel subclass, however, using a custom delegate is more powerful, and I didn't want to mix appearance-related code with my data model.)

In the example below, I started with the simple QListView / QAbstractListModel example, and added MyDelegate, a subclass of QItemDelegate. This class reimplements the paint() method to highlight selected items in red.

See also: Qt 4.3 QItemDelegate documentation

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

####################################################################
def main():
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

####################################################################
class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)

        # create objects
        list_data = [1,2,3,4]
        lm = MyListModel(list_data, self)
        de = MyDelegate(self)
        lv = QListView()
        lv.setModel(lm)
        lv.setItemDelegate(de)

        # layout
        layout = QVBoxLayout()
        layout.addWidget(lv)
        self.setLayout(layout)

####################################################################
class MyDelegate(QItemDelegate):
    def __init__(self, parent=None, *args):
        QItemDelegate.__init__(self, parent, *args)

    def paint(self, painter, option, index):
        painter.save()

        # set background color
        painter.setPen(QPen(Qt.NoPen))
        if option.state & QStyle.State_Selected:
            painter.setBrush(QBrush(Qt.red))
        else:
            painter.setBrush(QBrush(Qt.white))
        painter.drawRect(option.rect)

        # set text color
        painter.setPen(QPen(Qt.black))
        value = index.data(Qt.DisplayRole)
        if value.isValid():
            text = value.toString()
            painter.drawText(option.rect, Qt.AlignLeft, text)

        painter.restore()

####################################################################
class MyListModel(QAbstractListModel):
    def __init__(self, datain, parent=None, *args):
        """ datain: a list where each item is a row
        """
        QAbstractTableModel.__init__(self, parent, *args)
        self.listdata = datain

    def rowCount(self, parent=QModelIndex()):
        return len(self.listdata)

    def data(self, index, role):
        if index.isValid() and role == Qt.DisplayRole:
            return QVariant(self.listdata[index.row()])
        else:
            return QVariant()

####################################################################
if __name__ == "__main__":
    main()

How to install pyqt4 on ubuntu linux

Install and run "hello world" example
  • apt-cache search pyqt
  • sudo apt-get install python-qt4
  • Create a file ~/tmp/helloworld.py
    import sys
    from PyQt4.QtGui import *
    app = QApplication(sys.argv)
    button = QPushButton("Hello World", None)
    button.show()
    app.exec_()
    
  • Run it:
    python ~/tmp/helloworld.py
    You should see a window with a single button pop up.

Install additional examples
  • For more examples, install the python-qt4-doc package:
    sudo apt-get install python-qt4-doc
  • After installation, the examples are located at:
    /usr/share/doc/python-qt4-doc/examples

How to invert a dict in Python

Example 1: If the values in the dictionary are unique and hashable, then I can use Recipe 4.14 in the Python Cookbook, 2nd Edition.

def invert_dict(d):
    return dict([(v, k) for k, v in d.iteritems()])

d = {'child1': 'parent1',
     'child2': 'parent2',
     }
print invert_dict(d)
{'parent2': 'child2', 'parent1': 'child1'}

Example 2: If the values in the dictionary are hashable, but not unique, I can create a dict of lists as an inverse.

def invert_dict_nonunique(d):
    newdict = {}
    for k, v in d.iteritems():
        newdict.setdefault(v, []).append(k)
    return newdict

d = {'child1': 'parent1',
     'child2': 'parent1',
     'child3': 'parent2',
     'child4': 'parent2',
     }
print invert_dict_nonunique(d)
{'parent2': ['child3', 'child4'], 'parent1': ['child1', 'child2']}

Example 3: If I am starting with a dict of lists, where lists contain unique hashable items, I can create an inverse as shown below.

def invert_dol(d):
    return dict((v, k) for k in d for v in d[k])

d = {'child1': ['parent1'],
     'child2': ['parent2', 'parent3'],
     }
print invert_dol(d)
{'parent3': 'child2', 'parent2': 'child2', 'parent1': 'child1'}

Example 4: If I am starting with a dict of lists, where lists contain non-unique hashable items, I can create another dict of lists as an inverse.

def invert_dol_nonunique(d):
    newdict = {}
    for k in d:
        for v in d[k]:
            newdict.setdefault(v, []).append(k)
    return newdict

d = {'child1': ['parent1'],
     'child2': ['parent1'],
     'child3': ['parent2'],
     'child4': ['parent2'],
     'child5': ['parent1', 'parent2'],
     }
print invert_dol_nonunique(d)
{'parent2': ['child3', 'child4', 'child5'], 'parent1': ['child1', 'child2', 'child5']}

Notes on Python variable scope

Example 1: The difference between global and local variables

Global variables are accessible inside and outside of functions. Local variables are only accessible inside the function. In the example below, the function can access both the global and the local variable. However, trying to access the local variable outside the function produces an error.

global_var = 'foo'
def ex1():
    local_var = 'bar'
    print global_var
    print local_var

ex1()
print global_var
print local_var  # this gives an error
foo
bar
foo
Traceback (most recent call last):
  File "nested_scope.py", line 12, in 
    print local_var  # this gives an error
NameError: name 'local_var' is not defined

Example 2: How *not* to set a global variable

*Setting* a global variable from within a function is not as simple. If I set a variable in a function with the same name as a global variable, I am actually creating a new local variable. In the example below, var remains 'foo' even after the function is called.

var = 'foo'
def ex2():
    var = 'bar'
    print 'inside the function var is ', var

ex2()
print 'outside the function var is ', var
inside the function var is  bar
outside the function var is  foo

Example 3: How to set a global variable

To set the global variable inside a function, I need to use the global statement. This declares the inner variable to have module scope. Now var remains 'bar' after the function is called.

var = 'foo'
def ex3():
    global var
    var = 'bar'
    print 'inside the function var is ', var

ex3()
print 'outside the function var is ', var
inside the function var is  bar
outside the function var is  bar

Example 4: Nested functions

Scoping for nested functions works similarly. In the example below, the inner function can access both var_outer and var_inner. However, the outer function cannot access var_inner. Side note: the inner function is considered a closure if it makes reference to a non-global outside variable.

def ex4():
    var_outer = 'foo'
    def inner():
        var_inner = 'bar'
        print var_outer
        print var_inner
    inner()
    print var_outer
    print var_inner # this gives an error

ex4()
foo
bar
foo
Traceback (most recent call last):
  File "nested_scope.py", line 53, in 
    ex3()
  File "nested_scope.py", line 51, in ex3
    print var_inner # this gives an error
NameError: global name 'var_inner' is not defined

Example 5: How *not* to set an outer variable

Like Example 2, setting a variable in the inner function creates a new local variable instead of modifying the outer variable. In the example below, var in the outer function does not get changed to 'bar'.

def ex5():
    var = 'foo'
    def inner():
        var = 'bar'
        print 'inside inner, var is ', var
    inner()
    print 'inside outer function, var is ', var

ex5()
inside inner, var is  bar
inside outer function, var is  foo

Example 6: Another way to *not* set an outer variable

However, using the global keyword won't work in this case. global cause a variable to have module scope, but I want my variable to have the scope of the outer function. Per the Python 3000 Status Update, Python 3000 will have a nonlocal keyword to solve this problem. See PEP 3104 for more information about nonlocal and nested scopes. In the example below, var is still not changed to 'bar' in the outer function.

def ex6():
    var = 'foo'
    def inner():
        global var
        var = 'bar'
        print 'inside inner, var is ', var
    inner()
    print 'inside outer function, var is ', var

ex6()
inside inner, var is  bar
inside outer function, var is  foo

Example 7: A workaround until Python 3000 arrives

A workaround is to create an empty class to use as an additional namespace. Now the variable in the outer function can be set to 'bar'.

class Namespace: pass
def ex7():
    ns = Namespace()
    ns.var = 'foo'
    def inner():
        ns.var = 'bar'
        print 'inside inner, ns.var is ', ns.var
    inner()
    print 'inside outer function, ns.var is ', ns.var
ex7()
inside inner, ns.var is  bar
inside outer function, ns.var is  bar

Example 8: Alternative to Example 7

Update 2010-03-01: According to Alexander's comment below, this is not a good way to do things.

I learned about this method from Nihiliad's comment on my recursion example. To me, this seems like a more elegant alternative to the solution in Example 7.

def ex8():
    ex8.var = 'foo'
    def inner():
        ex8.var = 'bar'
        print 'inside inner, ex8.var is ', ex8.var
    inner()
    print 'inside outer function, ex8.var is ', ex8.var
ex8()
inside inner, ex8.var is  bar
inside outer function, ex8.var is  bar

Reference

Core Python Programming, Second Edition, Ch 11

See also

Correcting ignorance: learning a bit about Ruby blocks by Nick Coghlan

Saving a Python dict to a file using pickle

Per Programming Python, 3rd Edition, there are a number of methods to store persistent data with Python:

  • I often use flat files to read or write text (string) data using the os library.
  • Flat files are read sequentially, but dbm files allow for keyed access to string data
  • The pickle module can be used to store non-string Python data structures, such as Python dicts. However, the data is not keyed as with dbm files.
  • shelve files combine the best of the dbm and pickle methods by storing pickled objects in dbm keyed files.
  • I've read good things about the ZODB object-oriented database, but I don't know too much about it. Per the book, it is a more powerful alternative to shelves.
  • The final option is interfacing with a full-fledged SQL relational databases. As I mentioned before, Python 2.5 has an interface to SQLite as part of the standard distribution.

Here is an example using pickle which writes a Python dict to a file and reads it back again:

import pickle

# write python dict to a file
mydict = {'a': 1, 'b': 2, 'c': 3}
output = open('myfile.pkl', 'wb')
pickle.dump(mydict, output)
output.close()

# read python dict back from the file
pkl_file = open('myfile.pkl', 'rb')
mydict2 = pickle.load(pkl_file)
pkl_file.close()

print mydict
print mydict2

Results:
{'a': 1, 'c': 3, 'b': 2}
{'a': 1, 'c': 3, 'b': 2}

Python PyQt Tab Completion example

Here is an example Python GUI that implements tab completion. It uses the open source Qt 4.3 toolkit and PyQt 4.3 Python bindings.

A list of words is presented in a list box. As the user types, the list is shortened to show possible matches. If the user presses TAB, the input text is "completed" to the longest possible string match. This may be a whole word or a common substring of multiple words.

This example consists of two basic elements:

  • MyLineEdit is a subclass of the QLineEdit class. It is used as an input box to enter text. I needed to subclass QLineEdit because I needed to capture the TAB key press event for tab completion. (See this previous post.)
  • QListView and MyListModel implement a list with a simple model/view architechture. MyListModel is a subclass of QAbstractListModel. I implemented the required rowCount and data methods as well as a method called setAllData which replaces the entire existing data with a new list of data.

This example makes use of two SIGNALs:

  • The textChanged signal is emitted each time the user types a letter inside the QLineEdit box. It is connected to the text_changed method which updates the list of words in the QListView. MyListModel's setAllData method is used to update the data.
  • The tabPressed signal is a custom signal I added to my QLineEdit subclass. It is emitted each time the user presses the TAB key. This signal is connected the tab_pressed method which completes the input to the longest matching substring of the available words.

import sys
from PyQt4.QtCore import * 
from PyQt4.QtGui import * 

LIST_DATA = ['a', 'aardvark', 'aardvarks', 'aardwolf', 'aardwolves',
             'abacus', 'babel', 'bach', 'cache', 
             'daggle', 'facet', 'kabob', 'kansas']

#################################################################### 
def main(): 
    app = QApplication(sys.argv) 
    w = MyWindow() 
    w.show() 
    sys.exit(app.exec_()) 

#################################################################### 
class MyWindow(QWidget): 
    def __init__(self, *args): 
        QWidget.__init__(self, *args) 

        # create objects
        self.la = QLabel("Start typing to match items in list:")
        self.le = MyLineEdit()
        self.lm = MyListModel(LIST_DATA, self)
        self.lv = QListView()
        self.lv.setModel(self.lm)

        # layout
        layout = QVBoxLayout()
        layout.addWidget(self.la)
        layout.addWidget(self.le)
        layout.addWidget(self.lv) 
        self.setLayout(layout)

        # connections
        self.connect(self.le, SIGNAL("textChanged(QString)"),
                     self.text_changed)
        self.connect(self.le, SIGNAL("tabPressed"),
                     self.tab_pressed)

    def text_changed(self):
        """ updates the list of possible completions each time a key is 
            pressed """
        pattern = str(self.le.text())
        self.new_list = [item for item in LIST_DATA if item.find(pattern) == 0]
        self.lm.setAllData(self.new_list)

    def tab_pressed(self):
        """ completes the word to the longest matching string 
            when the tab key is pressed """

        # only one item in the completion list
        if len(self.new_list) == 1:
            newtext = self.new_list[0] + " "
            self.le.setText(newtext)

        # more than one remaining matches
        elif len(self.new_list) > 1:
            match = self.new_list.pop(0)
            for word in self.new_list:
                match = string_intersect(word, match)
            self.le.setText(match)

####################################################################
class MyLineEdit(QLineEdit):
    def __init__(self, *args):
        QLineEdit.__init__(self, *args)
        
    def event(self, event):
        if (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_Tab):
            self.emit(SIGNAL("tabPressed"))
            return True
        return QLineEdit.event(self, event)

#################################################################### 
class MyListModel(QAbstractListModel): 
    def __init__(self, datain, parent=None, *args): 
        """ datain: a list where each item is a row
        """
        QAbstractTableModel.__init__(self, parent, *args) 
        self.listdata = datain
 
    def rowCount(self, parent=QModelIndex()): 
        return len(self.listdata) 
 
    def data(self, index, role): 
        if index.isValid() and role == Qt.DisplayRole:
            return QVariant(self.listdata[index.row()])
        else: 
            return QVariant()

    def setAllData(self, newdata):
        """ replace all data with new data """
        self.listdata = newdata
        self.reset()

####################################################################
def string_intersect(str1, str2):
    newlist = []
    for i,j in zip(str1, str2):
        if i == j:
            newlist.append(i)
        else:
            break
    return ''.join(newlist)

####################################################################
if __name__ == "__main__": 
    main()

How to use *args and **kwargs in Python

Or, How to use variable length argument lists in Python.

The special syntax, *args and **kwargs in function definitions is used to pass a variable number of arguments to a function. The single asterisk form (*args) is used to pass a non-keyworded, variable-length argument list, and the double asterisk form is used to pass a keyworded, variable-length argument list. Here is an example of how to use the non-keyworded form. This example passes one formal (positional) argument, and two more variable length arguments.

def test_var_args(farg, *args):
    print "formal arg:", farg
    for arg in args:
        print "another arg:", arg

test_var_args(1, "two", 3)

Results:

formal arg: 1
another arg: two
another arg: 3

Here is an example of how to use the keyworded form. Again, one formal argument and two keyworded variable arguments are passed.

def test_var_kwargs(farg, **kwargs):
    print "formal arg:", farg
    for key in kwargs:
        print "another keyword arg: %s: %s" % (key, kwargs[key])

test_var_kwargs(farg=1, myarg2="two", myarg3=3)

Results:

formal arg: 1
another keyword arg: myarg2: two
another keyword arg: myarg3: 3

Using *args and **kwargs when calling a function

This special syntax can be used, not only in function definitions, but also when calling a function.

def test_var_args_call(arg1, arg2, arg3):
    print "arg1:", arg1
    print "arg2:", arg2
    print "arg3:", arg3

args = ("two", 3)
test_var_args_call(1, *args)

Results:

arg1: 1
arg2: two
arg3: 3

Here is an example using the keyworded form when calling a function:

def test_var_args_call(arg1, arg2, arg3):
    print "arg1:", arg1
    print "arg2:", arg2
    print "arg3:", arg3

kwargs = {"arg3": 3, "arg2": "two"}
test_var_args_call(1, **kwargs)

Results:

arg1: 1
arg2: two
arg3: 3

See also (updated 2009-04-12)


Reference: Core Python Programming, Second Edition, Section 11.6

How to find the intersection and union of two lists in Python

My friend Bill had previously alerted me to the coolness of Python sets. However I hadn't found opportunity to use them until now. Here are three functions using sets to remove duplicate entries from a list, find the intersection of two lists, and find the union of two lists. Note, sets were introduced in Python 2.4, so Python 2.4 or later is required. Also, the items in the list must be hashable and order of the lists is not preserved.

For more information on Python sets, see the Library Reference.

""" NOTES:
      - requires Python 2.4 or greater
      - elements of the lists must be hashable
      - order of the original lists is not preserved
"""
def unique(a):
    """ return the list with duplicate elements removed """
    return list(set(a))

def intersect(a, b):
    """ return the intersection of two lists """
    return list(set(a) & set(b))

def union(a, b):
    """ return the union of two lists """
    return list(set(a) | set(b))

if __name__ == "__main__": 
    a = [0,1,2,0,1,2,3,4,5,6,7,8,9]
    b = [5,6,7,8,9,10,11,12,13,14]
    print unique(a)
    print intersect(a, b)
    print union(a, b)

Results:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[8, 9, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

How to capture the Tab key press event with PyQt 4.3

Normally, pressing the TAB key changes focus among widgets. However, I would like to use the TAB key for other purposes (e.g. tab completion). To gain control of the TAB key press event, I need to subclass my widget and reimplement the QObject.event() event handler. I don't need to re-write the entire event handler. I only need to process TAB key press events. I will pass all other events to the default event handler. The example below subclasses the QLineEdit widget and reimplements the event() method. Pressing the TAB key inside this new widget prints out the text "tab pressed" inside a second QLineEdit box.

The Events and Event Filters Trolltech QT documentation has a good explanation of how this works. My example shows how to use Python and PyQt instead of C++.

import sys
from PyQt4.QtCore import * 
from PyQt4.QtGui import * 

#################################################################### 
def main(): 
    app = QApplication(sys.argv) 
    w = MyWindow() 
    w.show() 
    sys.exit(app.exec_()) 

####################################################################
class MyWindow(QWidget): 
    def __init__(self, *args): 
        QWidget.__init__(self, *args)

        # create objects
        self.la = QLabel("Press tab in this box:")
        self.le = MyLineEdit()
        self.la2 = QLabel("\nLook here:")
        self.le2 = QLineEdit()

        # layout
        layout = QVBoxLayout()
        layout.addWidget(self.la)
        layout.addWidget(self.le)
        layout.addWidget(self.la2)
        layout.addWidget(self.le2)
        self.setLayout(layout)

        # connections
        self.connect(self.le, SIGNAL("tabPressed"),
                     self.update)

    def update(self):
        newtext = str(self.le2.text()) + "tab pressed "
        self.le2.setText(newtext)

####################################################################
class MyLineEdit(QLineEdit):
    def __init__(self, *args):
        QLineEdit.__init__(self, *args)
        
    def event(self, event):
        if (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_Tab):
            self.emit(SIGNAL("tabPressed"))
            return True

        return QLineEdit.event(self, event)

####################################################################
if __name__ == "__main__": 
    main()