Python Workshop: Part 2

Before you design a GUI with Python, you need to understand a few basics about object-oriented programming. That's because object-oriented programming is what you'll need to create a graphical user.

Shirts Are Simply Objects

Because everything is an object in Python, you often need to design your own objects before you can use them in your program. Admittedly, this is a somewhat abstract concept. I'll use an example from daily life to make it easier to understand. A shirt is an object. Object-oriented code can represent this shirt as an abstract concept as shown in Listing 1. As always, you can find the source code for the examples on the Ubuntu User website [1].

Listing 1

shirt.py

01 # -*- coding: utf-8 -*-
02
03 class shirt(object):
04     """A shirt that will be washed repeatedly.
05     """
06     def __init__(self, size, color):
07         self.size = size
08         self.color = color
09         self.washes = 0
10     def wash(self):
11        """Count how often I was washed.
12        """
13        self.washes += 1

Right on the first line, you specify the file encoding with # - * - coding: utf-8 - * - , which allows you to use characters from all languages in your (unicode) strings and comments. Of course, you need to save this file in the indicated encoding or the program will fail to start. How you go about this depends on the editor you use. With Gedit, you can use the menu item File | Save as and choose the encoding.

The line class Shirt(object) defines the new class, Shirt , which inherits from the base class object . Although there are good reasons why you need to put object there, you can take it as a convention without my going into the details. The keyword class defines new types of objects. Classes are at the core of object-oriented programming. The box "Class, Instance, Attribute, Method" provides some definitions of the different objects that are important when working with classes.

Class, Instance, Attribute, Method

Class: Classes define new datatypes and help represent things in real life in programs. The Shirt class has a size , a color , and a number of washes .

Instance: A white shirt in size 16 would be an instance of the Shirt class. There can be any number of instances in a program. The class thus defines general criteria for shirts and instances are the concrete manifestation of these criteria.

Attribute: Classes and instances have attributes. The shirt_1 instance has the attributes size , color , and number of washes , which have specific values. Every instance, that is, every shirt, has individual attributes and can have a different color and size.

Method: Technically speaking, methods are functions that you define inside a class. A method usually does something. The wash() method "washes" your shirts and increments the wash counter.

Next comes the method __init__ that contains the attributes size , color , and washes . Python automatically calls __init__ and initializes the attributes with values when you create a new instance of the class.

You're already familiar with methods like __init__ because methods are functions that you define inside a class. Remember, for example, the function get_total_size() from the previous workshop [2]. The first argument to a method is always self. , which is a placeholder for the instance you create later. You need a placeholder here, because when you define a class and its methods, you don't know the instance yet. The wash() method defined on line 10 increments the counter for the washes attribute.

The next step is to go to the interactive prompt and wash a digital shirt. Import the module shirt (the file shirt.py ) and create a new instance in the Shirt() class defined in it:

>>> import shirt
>>> shirt_1 = shirt.Shirt(16, 'white')

You then access the attributes color and size of the instance shirt_1 :

>>> shirt_1.color
'white'
>>> shirt_1.size
42

You can wash the digital shirt and see the results as the wash counter increments:

>>> shirt_1.washes
0
>>> shirt_1.wash()
>>> shirt_1.washes
1
>>> shirt_1.wash()
>>> shirt_1.washes
2

You then create a second instance:

>>> shirt_2 = shirt.Shirt('L', 'green')
>>> shirt_2.size
'L'
>>> shirt_2.color
'green'

And, the wash results

>>> shirt_2.washes
0
>>> shirt_2.wash()
>>> shirt_2.washes
1

will be shown as before.

A Graphical User Interface

With Python, you can use many different GUI libraries, among them GTK, WxWidgets, and Qt. Basically, you use all these libraries in a similar way using the same programming style (see the "Programming Paradigms" box).

Programming Paradigms

You can use various programming styles and paradigms to solve problems in programs. In the previous article, you used the procedural paradigm (perhaps without knowing it). Such a program consists of instructions that the computer executes one at a time in a sequence of steps. A procedure is a self-contained unit or, in this case, a function beginning with def . These procedures structure a program, which executes them in sequence.

Objects, such as instances, are of great importance in object-oriented programming. Instances contain data in the form of attributes (the size and color of shirts) and behave in a certain way (wash shirts). These objects interact with each other and often can represent real-world objects better than in other programming styles.

For your graphical interface, you'll use event-driven programming. Your program won't run sequentially but will pause and respond to triggering events. For example, you can call the method calculate() by clicking the Calculate Size button. This triggers an event. During programming, it isn't clear yet if and when you will access a particular method when you use the program. For example, if you close the program right after opening it, there may not be an event to execute the method.

This example uses PySide [3], which makes the powerful Qt library accessible for Python programmers. PySide is open source (LGPL) and works on Mac OS X as well as on Windows.

To install PySide, enter the command:

sudo apt-get install python-pyside

Installation requires the administrator password and takes some time. Once you've installed it, type >>> import PySide at the interactive prompt. If you don't get an import error, you have successfully installed this library.

The Source Code…

The source code for your GUI program is in Listing 2, and I'll describe it later line by line.

Listing 2

recursive_gui.py

01 #!/usr/bin/env python
02 # -*- coding: utf-8 -*-
03
04 """GUI for recursive calculation of the size of
05    all files in a directory tree.
06 """
07
08 import sys
09 from PySide import QtGui
10 from recursive_func import get_total_size
11
12 class Form(QtGui.QDialog):
13   """Main Window.
14   """
15   def __init__(self, parent=None):
16     super(Form, self).__init__(parent)
17     self.setWindowTitle(u"Recurser")
18     self.path = QtGui.QLineEdit(u"<textentry>")
19     self.button = QtGui.QPushButton(u"Calculate Size")
20     self.res = QtGui.QLabel(u"Result")
21     layout = QtGui.QVBoxLayout()
22     layout.addWidget(self.path)
23     layout.addWidget(self.button)
24     layout.addWidget(self.res)
25     self.setLayout(layout)
26     self.button.clicked.connect(self.calculate)
27
28   def calculate(self):
29     """Display the calculated result.
30     """
31     path = self.path.text()
32     self.res.setText(u'Total: %d' % get_total_size(path))
33
34 def show_app():
35   """Display the window.
36   """
37   app = QtGui.QApplication(sys.argv)
38   form = Form()
39   form.show()
40   sys.exit(app.exec_())
41
42 if __name__ == '__main__':
43   show_app()

Download the Python script from the Internet [1] and make it executable with:

chmod +x recursive_gui.py

If you enter the code yourself, be sure to save the file with UTF-8 encoding. Then, call the script using ./recursive_gui.py to get the results in Figure 1. As in the previous workshop, the program adds up all the file sizes in a specified directory and all its subdirectories. In the text field, enter the name of the directory and click the Calculate Size button (Figure 2).

Figure 1: The GUI developed with the PySide toolkit is platform-independent and awaits user entry.
Figure 2: Enter the directory name and click "Calculate Size" to get the total file sizes.

Step by Step

Next, I'll look at this Python program in detail. Line 01 with #!/usr/bin/env python points to the command python . The #! is pronounced shebang and works on all Unix-like systems. The system knows right away which language is in use and where to find the matching interpreter.

After the obligatory doc string, starting at line 08, you import several modules. The module sys is from the standard library; you usually list this module first. Next, you import the module QtGui from the PySide package that provides all functions needed for the GUI application. Finally, you import the recursive_func module that you used in the last workshop that's in the download archive and whose get_total_size() function you're already familiar with.

Even though recursive_func is short, you should resist any temptation of defining the get_total_size() function inside the module with the GUI. It's good practice to separate the source code for the graphical interface as much as possible from the other program functions. Distributing it among different files, as shown here, can help in that respect. That way you can apply a command-line interface without much trouble or use the get_total_size() function in another program.

The class Form(QtGui.QDialog): section starting at line 12 defines the new form class that inherits the methods and attributes from QtGui.QDialog (the QDialog class from the QtGui module). Thus the class Form can get everything from QDialog , and that's quite a bit, as Listing 3 shows. The dir() command in the listing creates a list with all the names of the attribute belonging to the class, and len() determines how long the list is: There are 348 attributes.

Listing 3

Attributes of QDialog

>>> from PySide import QtGui
>>> len(dir(QtGui.QDialog))
348
>>> print dir(QtGui.QDialog)
<long list of names>

The next step is to construct the graphical interface (Figure 3). That all happens in the indented code after def __init__(self, parent=None): on line 15. Python always executes the __init__() method when you create an instance of a class. That happens on line 38 with form = Form and you get the new instance form of the class Form . This makes Python execute all the steps in the __init__ method on line 15.

Figure 3: The form class defines the elements that appear in the graphical user interface.

The expression super(Form, self).__init__(parent) on line 16 is necessary so that Python correctly initializes the QtGui.QDialog class. On lines 17 through 20, PySide creates the different graphical elements step by step. After setting the name of the window with setWindowTitle() , the method adds three GUI elements: a text entry field with QLineEdit() , a button with QPushButton() , and the results label with QLabel() . The u before text such as in u"Result" indicates Unicode text so that you can use special language characters.

From line 21, the program puts the three GUI elements into the so-called QVBoxLayout() container that organizes the elements vertically in the layout (Figure 3). You first create an instance of the container and add the elements with layout.addWidget() . The code on line 25 self.setLayout() puts the container in the empty program window.

Once you've defined the graphical elements, make sure that something happens when the user clicks the Calculate Size button. In this case, Python should call the calculate() method. This is specified on line 26 with self.button.clicked.connect(self.calculate) .

With self.path.text() on line 31, calculate gets the text from the entry field. This text must be a valid pathname. The function get_total_size() on line 32 uses the pathname as argument, calculates the file sizes, and delivers the result to the method self.res.setText() , which renders the result in the graphical interface.

At this point, you've defined all essential functions and classes, but you still have to start the application. The show_app() function on the last line does that for you. First, it creates an instance of a QApplication and then an instance of the class Form . The call to form.show() on line 39 puts the application on the screen.

In the next line, sys.exit() calls the Qt application using (app.exec_() ). The function sys.exit() allows to give the calling application an exit status when the program finishes. A status code of 0 indicates success while other codes indicate particular errors. The box "Exit Status in PySide" provides more details.

Exit Status in PySide

PySide is a Python wrapper around Qt, which is written in C++. Qt returns codes for the exit status as numbers. Each number other than zero stands for a particular error. The calling application can use the returned error after the GUI program terminates to do something useful. For this case, the error will go to the terminal window where you typed the command to start your program. You can retrieve the last exit status with echo $? . It should be zero if everything went as expected.

Infos

  1. Source code: ftp://ftp.linux-magazin.de/pub/listings/ubuntu-user.com/
  2. "Python Workshop" by Mike Mueller, Ubuntu User , Issue 15, pg. 50
  3. PySide: http://www.pyside.org
  4. Python Academy: http://www.python-academy.com

The Author

Mike Müeller is the managing director of the Python Academy [4] and an experienced Python trainer. Since he discovered Python in 1999, it has become his favorite programming language. He is a regular speaker at national and international Python conferences and has introduced many students to the advantages of Python.