Developing applications - Python in easy steps (2014)

Python in easy steps (2014)

10

Developing applications

This chapter brings together elements from previous chapters to build a complete Python application.

Generating random numbers

Planning the program

Designing the interface

Assigning static properties

Initializing dynamic properties

Adding runtime functionality

Testing the program

Freezing the program

Deploying the application

Summary

Generating random numbers

The graphical application developed on subsequent pages of this book will generate six random numbers within a specific range. Initially, its functionality can be developed as a console application then transferred later to illustrate how it can be applied to graphical widget components.

image

Floating-point numbers cast from the float data type to the int data type by the built-in int() function get truncated at the decimal point.

The standard Python library has a random module that provides methods to generate pseudo-random numbers. The current system time is used by default to “seed” the random generator whenever it gets initialized - so it does not repeat its selections.

A pseudo-random floating-point number from 0.0 to 1.0 can be generated by calling the random() method from the random module. The range of generated numbers can be modified using the * multiplication operator to specify a maximum value and can be rounded down to integer values using the built-in int() function. For example, to generate an integer within the range of zero to nine:

int( random.random() * 10 )

Or to generate a whole number within the range of one to ten:

int( random.random() * 10 ) + 1

This statement could be used in a loop to generate multiple random integers within a given range but any number may be repeated in that output - there is no guaranteed uniqueness. Instead, multiple unique random integers within a given range can be generated by the sample() method from the random module. This requires two arguments to specify the range and the number of unique integers to be returned. It is convenient to use the built-in range() function to specify a maximum value. For example, to generate six unique numbers within the range of zero to nine:

random.sample( range( 10 ) , 6 )

Or to generate six unique numbers within the range of one to ten:

random.sample( range( 1 , 11 ) , 6 )

This technique could represent a random lottery entry by choosing, say, six unique numbers between one and 49.

image

The range() function can specify start and end values. If no starting value is supplied, zero is assumed by default.

imageLaunch a plain text editor then begin a Python program by importing two functions from the “random” module

from random import random , sample

image

sample.py

imageNext, assign a random floating-point number to a variable then display its value
num = random()

print( ‘Random Float 0.0-1.0 :, num )

imageNow, multiply the floating-point number and cast it to become an integer then display its value

num = int( num * 10 )

print( ‘Random Integer 0 - 9 : ‘ , num )

imageAdd a loop to assign multiple random integers to a list then display the list items

nums = [] ; i = 0

while i < 6 :

nums.append( int( random() * 10 ) + 1 )

i += 1

print( ‘Random Multiple Integers 1-10 :’ , nums )

imageFinally, assign multiple unique random integers to the list then display the list items

nums = sample( range( 1, 49 ) , 6 )

print( ‘Random Integer Sample 1 - 49 : ‘ , nums )

imageSave the file then execute the program several times - to see the generated random numbers

image

image

The random.sample() function returns a list but does not actually replace any elements in the specified range.

Planning the program

When creating a new graphical application it is useful to first spend some time planning its design. Clearly define the program’s precise purpose, decide what application functionality will be required, then decide what interface widgets will be needed.

image

A plan for a simple application to pick numbers for a lottery entry might look like this:

Program purpose

The program will generate a series of six unique random numbers in the range 1-49 and have the ability to be reset

Functionality required

A function to generate and display six unique random numbers

A function to clear the last six random numbers from display

Interface widgets needed

One non-resizable window to contain all other widgets and to display the application title

One Label widget to display a static application logo image - just to enhance the appearance of the interface

Six Label widgets to dynamically display the generated series of unique random numbers - one number per Label

One Button widget to generate and display the numbers in the Label widgets when this Button gets clicked. This Button will not be enabled when the numbers are on display

One Button widget to clear the numbers on display in the Label widgets when this Button gets clicked. This Button will not be enabled when the numbers are not on display

Having established a program plan means you can now produce the application basics by creating all the necessary widgets.

image

Toggle the value of a Button widget’s state property from NORMAL to DISABLED to steer the user - in this case the application must be reset before a further series of unique random numbers can be generated.

imageLaunch a plain text editor then begin a Python program by importing all features from the “tkinter” module

# Widgets:

from tkinter import *

image

lotto(widgets).py

imageNext, add statements to create a window object and an image object

window = Tk()

img = PhotoImage( file = ‘logo.gif’ )

image

lotto.gif

imageNow, add statements to create all the necessary widgets

imgLbl = Label( window, image = img )

label1 = Label( window, relief = ‘groove’, width = 2 )
label2 = Label( window, relief = ‘groove’, width = 2 )
label3 = Label( window, relief = ‘groove’, width = 2 )
label4 = Label( window, relief = ‘groove’, width = 2 )
label5 = Label( window, relief = ‘groove’, width = 2 )
label6 = Label( window, relief = ‘groove’, width = 2 )
getBtn = Button( window )

resBtn = Button( window )

imageThen, add the widgets to the window using the grid layout manager - ready to receive arguments to specify how the widgets should be positioned at the design stage next

# Geometry:

imgLbl.grid()

label1.grid()

label2.grid()

label3.grid()

label4.grid()

label5.grid()

label6.grid()

getBtn.grid()

resBtn.grid()

imageFinally, add a loop statement to sustain the window

# Sustain window:

window.mainloop()

imageSave the file then execute the program - to see the window appear containing all the necessary widgets

image

The relief property specifies a border style and the width property specifies the label width in character numbers.

image

Designing the interface

Having created all the necessary widgets, on the previous page, you can now design the interface layout by adding arguments to specify how the widgets should be positioned. A horizontal design will position the logo Label on the left, and on its right all six other Labels in a row with both Buttons below this. The grid layout manager, which positions widgets in rows and columns, can easily produce this design by allowing the logo Label to span a row containing all six other Labels and also a row containing both Buttons. One Button can span four columns and the other Button can span two columns, arranged like this:

image

imageEdit the program started on the previous page - firstly by positioning the Label containing the logo in the first column of the first row, and have it span across the second row

# Geometry:

imgLbl.grid( row = 1, column = 1, rowspan = 2 )

image

lotto(layout).py

imageNext, position a Label in the second column of the first row and add 10 pixels of padding to its left and right

label1.grid( row = 1, column = 2, padx = 10 )

imageNow, position a Label in the third column of the first row and add 10 pixels of padding to its left and right

label2.grid( row = 1, column = 3, padx = 10 )

imagePosition a Label in the fourth column of the first row and add 10 pixels of padding to its left and right

label3.grid( row = 1, column = 4, padx = 10 )

image

The grid layout manager’s rowspan and columnspan properties work like the HTML rowspan and colspan table cell attributes.

imagePosition a Label in the fifth column of the first row and add 10 pixels of padding to its left and right

label4.grid( row = 1, column = 5, padx = 10 )

imagePosition a Label in the sixth column of the first row and add 10 pixels of padding to its left and right

label5.grid( row = 1, column = 6, padx = 10 )

imagePosition a Label in the seventh column of the first row then add 10 pixels of padding to the left side of the Label and 20 pixels of padding to the right side of the Label

label6.grid( row = 1, column = 7, padx = ( 10, 20 ) )

imageNext, position a Button in the second column of the second row and have it span across four columns

getBtn.grid( row = 2, column = 2, columnspan = 4 )

imageNow, position a Button in the sixth column of the second row, and have it span across two columns

resBtn.grid( row = 2, column = 6, columnspan = 2 )

imageSave the file then execute the program - to see the window appear containing all the necessary widgets now arranged in your grid layout design

image

The window size is automatically adjusted to suit the grid contents and the Button widgets are automatically centered in the spanned column width.

image

Additional padding to the right of the Label in the final column of the first row extends the window width to simply create a small right-hand margin area.

image

The Buttons will expand to fit static text that will appear on each Button face - specified in the next stage.

Assigning static properties

Having arranged all the necessary widgets in a grid layout, on the previous page, you can now assign static values to the widgets. These values will not change during execution of the program.

imageModify the program on the previous page by inserting a new section just before the final loop statement, which begins with a statement specifying a window title

# Static Properties:

window.title( ‘Lotto Number Picker’ )

image

lotto(static).py

imageNext, add a statement to prevent the user resizing the window along both the X axis and the Y axis - this will disable the window’s “resize” button

window.resizable( 0, 0 )

imageNow, add a statement to specify text to appear on the face of the first Button widget

getBtn.configure( text = ‘Get My Lucky Numbers’ )

imageThen, add a statement to specify text to appear on the face of the second Button widget

resBtn.configure( text = ‘Reset’ )

imageSave the file then execute the program - to see the window now has a title, its resize button is disabled, and the buttons have now been resized to suit their text

image

image

The widget’s configure() method allows properties to be subsequently added or modified after they have been created.

Initializing dynamic properties

Having specified values for static properties, on the facing page, initial values can now be specified for those properties whose values will change dynamically during execution of the program.

imageModify the program on the facing page by inserting another new section just before the final loop statement, which specifies that each small empty Label should initially display an ellipsis

# Initial Properties:

label1.configure( text = ‘...’ )

label2.configure( text = ‘...’ )

label3.configure( text = ‘...’ )

label4.configure( text = ‘...’ )

label5.configure( text = ‘...’ )

label6.configure( text = ‘...’ )

image

lotto(initial).py

imageNext, add a statement to specify that the second Button widget should initially be disabled

resBtn.configure( state = DISABLED )

imageSave the file then execute the program - to see each small Label now displays an ellipsis and that the “Reset” Button has been disabled

image

image

Button states are recognized by tkinter constants of DISABLED (off), NORMAL (on), or ACTIVE (pressed).

Adding runtime functionality

Having created code to initialize dynamic properties, on the previous page, you can now add runtime functionality to respond to clicks on the Button widgets during execution of the program.

imageModify the program on the previous page by inserting one more new sections just before the final loop statement, which begins by making the sample() function available from the “random” module

# Dynamic Properties:

from random import sample

image

lotto.py

imageNext, define a function that generates and assigns six unique random numbers to the small Labels and reverses the state of both Buttons

def pick() :

nums = sample( range( 1, 49 ), 6 )
label1.configure( text = nums[0] )
label2.configure( text = nums[1] )
label3.configure( text = nums[2] )
label4.configure( text = nums[3] )
label5.configure( text = nums[4] )
label6.configure( text = nums[5] )
getBtn.configure( state = DISABLED )
resBtn.configure( state = NORMAL )

imageNow, define a function to display an ellipsis on each small Label and revert both Buttons to their initial states

def reset() :

label1.configure( text = ‘...’ )
label2.configure( text = ‘...’ )
label3.configure( text = ‘...’ )
label4.configure( text = ‘...’ )
label5.configure( text = ‘...’ )
label6.configure( text = ‘...’ )
getBtn.configure( state = NORMAL )
resBtn.configure( state = DISABLED )

imageThen, add statements to nominate the relevant function to be called when each Button is pressed by the user

getBtn.configure( command = pick )

resBtn.configure( command = reset )

image

These steps provide comparable functionality to that of the console application here.

imageFinally, save the file - the complete program should look like that shown opposite

image

image

It is convention to place all import statements at the start of the script but they can appear anywhere, as listed here.

image

The color highlighting in the IDLE editor differs from that used throughout this book - but the code precisely compares to that listed in this chapter’s steps.

Testing the program

Having worked through the program plan, on the previous pages, the widgets needed and functionality required have now been added to the application - so it’s ready to be tested.

imageLaunch the application and examine its initial appearance

image

Static text appears on the window title bar and on the Button widgets, the window’s resize button is disabled, the small Labels contain their initial ellipsis text values, and the “Reset” button is in its initial disabled state.

imageNext, click the “Get My Lucky Numbers” Button widget - to execute all the statements within the pick() function

image

image

No number is repeated in any series because the random module’s sample() function returns a set of unique random integers.

A series of numbers within the desired range is displayed and the Button states have changed as required - a further series of numbers cannot be generated until the application has been reset.

imageMake a note of the numbers generated in this first series for comparison later

imageClick the “Reset” Button widget - to execute all the statements within the reset() function and see the application resume its initial appearance as required

image

imageClick the “Get My Lucky Numbers” Button widget again - to execute its pick() function again and confirm that the new series of numbers differ from the first series

image

imageFinally, restart the application and click the “Get My Lucky Numbers” Button widget once more - and confirm that this first series numbers are different to those noted in the first series when the application last ran

image

image

The series of generated numbers are not repeated each time the application gets launched because the random generator is seeded by the current system time - which is different each time the generator gets called upon.

Freezing the program

Having satisfactorily tested the application, on the previous page, you may wish to distribute it for use on other computers where the Python interpreter has not necessarily been installed. To ensure the application will execute successfully without the Python interpreter, your program files can be “frozen” into a bundle that includes an executable (.exe) file.

image

As the default base is set for a console application graphical applications require a different base on Windows.

The “cx_Freeze” tool is a free set of scripts and modules for freezing Python scripts into executables for Windows, Mac, or Linux. This tool is cross-platform and should work on any platform that Python itself works on. It can be freely downloaded from http://cx-freeze.sourceforge.net where MSI installers are available for Windows and RPM installers for Linux, both for 32 bit and 64 bit systems - simply choose the appropriate version for your system then download it and run the installer.

The cx_Freeze tool uses Python’s “distutils” package and this requires a setup script to describe your module distribution so it will bundle appropriate support for your application. The setup script is traditionally named setup.py and consists mainly of a call to a cx_Freeze setup() function - supplying information as argument pairs. This specifies any required build options, such as image files or modules to be included, and identifies the executable script and system platform type. For example, the setup script for the application developed throughout this chapter must include the logo image file logo.gif and specify the final script named lotto.py as the executable script.

A setup script can be executed with the command build to create a sub-directory named “build” containing a further sub-directory named starting with the letters “exe.” and ending with the typical identifier for your system, such as “win32-3.3”.

imageLaunch a plain text editor then begin a Python setup script by making available the “sys” module and items from the “cx_Freeze” module
import sys
from cx_Freeze import setup, Executable

image

setup.py

imageNext, add statements to identify the base platform in use

base = None
if sys.platform ==
‘win32’ : base = ‘Win32GUI’

imageNow, add a statement listing the include options

opts = { ‘include_files’ : [ ‘logo.gif’ ] , ‘includes’ : [ ‘re’ ] }

imageFinally, add a call to the setup() function passing all information as arguments

setup( name = ‘Lotto’ ,

version = ‘1.0’ ,

description = ‘Lottery Number Picker’ ,
author =
‘Mike McGrath’ ,

options = { ‘build_exe’ : opts } ,

executables = [ Executable( ‘lotto.py’, base= base ) ] )

imageSave the file alongside the application files then run the setup script to build the distributable bundle

image

imageWait until the build process creates a bundle of files in the “build” sub-directory then copy the whole bundle onto portable media, such as a USB flash drive

imageNow, copy the bundle onto another computer where Python may not be present and run the executable file - to see the application launch

image

lotto.exe

image

image

The ‘re’ (regular expressions) module is manually included as a build option here, only because at the time of writing cx_Freeze fails to include it automatically.

Deploying the application

Applications developed in the Python language can be deployed on Windows systems using the cx_Freeze tool, introduced on the previous page, to create a simple MSI installer.

This employs exactly the same setup script as that used to build a distributable bundle of files - listed in the previous example and illustrated below:

image

image

On Mac OS X, you can use bdist_dmg to build a Mac disk image.

The setup script can be executed with the command bdist_msi, rather than a build command, to create a sub-directory called “dist” containing an MSI installer for your application. The installer name comprises the application name and version then your system version.

imageSave the setup script alongside the application files then run the script to create the Windows installer

image

imageWait until the process creates the installer in a “dist” sub-directory then copy the installer onto portable media, such as a USB flash drive

image

You can discover more on cx_Freeze online at cx_freeze.readthedocs.org

imageNow, copy the installer onto another Windows computer where Python may not be present and run the installer

image

Lotto-1.0-win32.msi

imageThen, select an installation location, or accept the suggested default location

image

imageWhen the installer has finished copying files, navigate to your chosen installation location and run the executable file - to see the application launch

image

lotto.exe

image

Summary

The standard Python library has a random module that provides functions to generate pseudo-random numbers

A pseudo-random floating-point number from 0.0 to 1.0 can be generated by the random module’s random() function

Multiple unique random integers within a given range can be generated by the random module’s sample() function

A program plan should define the program’s purpose, required functionality, and interface widgets needed

In designing a program interface, the grid() layout manager positions widgets in rows and columns

Static properties do not change during execution of a program

Dynamic properties do change during execution of a program using runtime functionality to respond to a user action

Upon completion, a program should be tested to ensure it performs as expected in every respect

Program files can be “frozen” into a bundle for distribution to other computers where the Python interpreter is not present

The cx_Freeze tool uses Python’s “disutils” package to freeze programs into executables for Windows, Mac, or Linux

A setup script describes your module distribution so cx_Freeze will bundle appropriate support for the application

When a setup script is executed with the build command, a distribution bundle is created that includes an executable file

Applications can be deployed on Windows systems using the cx_Freeze tool to create a simple installer

When a setup script is executed with the bdist_msi command, an MSI installer is created that will copy the distribution bundle onto the host computer, including an executable file

image

image

image