Beruflich Dokumente
Kultur Dokumente
this site
belmontpython
Object-Oriented Programming, a unit
converter program, and The Model-View-
Controller organization
Contents
1 Object-Oriented Programming
2 A Units Converter
3 The Model-View-Controller organization
O b je c t - O r ie n t e d P r o g r a m m in g
This very brief introduction would immediately be pounced on and ripped apart by
any of the host of dedicated writers on the subject, and I suggest you consult
them, in particular Toby Donaldson's book referenced earlier.
So far, we've studied some simple and some complicated Python applications,
but without ever mentioning OOP. Is Python an OOP language? Absolutely! But
as with other aspects, Python hides the OOP machinery, to make programming as
simple as possible. Python Functions, identified by def() statements, are actually
the Methods of OOP terminology, written in an equivalent but simpler form. To
demonstrate this, we'll begin with a fairly simple application, called Converter,
without exposing its OOP structure, and then rewrite exactly the same application
in object-oriented language.
A U n it s C o n v e r t e r
Nowadays you can find unit converters-- pounds to kilograms, miles to kilometers,
Fahrenheit to Celsius, and the like-- on the Web. We'll create a version that might
be useful if you needed to convert something but didn't have access to the
internet. The Converter uses several Controls that we've already studied: an
Entry, into which the user enters a value in the old system of units, two
Comboboxes, one for selecting the old unit, the other for selecting an allowed
new unit-- you can't convert pounds to feet, for example-- and a Label to display
the converted value in the new unit. We also want to check the validity of the
typed-in value in order to avoid an error if Python tries to convert something
invalid to an actual number. We'll do this with a parsing function called
entrytype() that rejects invalid entries such as, say, 1.2x or 1.2.3, but also allows
entries in scientific notation like 5.2e+3. Entries have a slightly unexpected
property: the Entry.get() function returns the contents up to, but NOT including, the
most recently entered character, even though it is displayed on the screen, so to
check the entire current entry, we have to get the last character from a separate
property of the Entry.
The procedure is as usual for our GUI examples: the program creates the controls,
and then calls the root mainloop function to wait for a user action. When the user
types something, the associated event handler passes information to a process
function that carries out any indicated actions, including creating a list of allowed
unit conversions, checking the Entry for validity, converting the value if valid to a
real number, and displaying the result in the Label.
Download converter.py and run it. Note that when the old value is complete and
valid, the conversion may take place even before the return or enter key is
pressed. Try entering illegal values, which will cause the entry to turn red until the
error is corrected.
Here's the program:
icunittype = 0
cboxindices = []
controls = []
# Create arrays of conversion data: the second, third,
... entries contain the data
# to allow them to be converted to and from the first
entry
for ic in range(len(controldata)):
if controldata[ic][0] == 'unit type': icunittype = ic
if controldata[ic][0] == 'old value': icoldvalue = ic
if controldata[ic][0] == 'old unit': icoldunit = ic
if controldata[ic][0] == 'new value': icnewvalue = ic
if controldata[ic][0] == 'new unit': icnewunit = ic
def onkeypress(event):
# Event handler for entry key pressed passes source and
key to process
st = event.widget.get()
ch = event.char
process(icoldvalue, st, ch)
def onselection(event):
# Event handler for Combobox selected, calls
Converter.process with the control index
# Set up combobox lists if unit type changes
for controlindex in range(len(controls)):
if event.widget == controls[controlindex]: break
unittypeindex = icunittype
if controlindex == unittypeindex:
setupcomboboxes(1)
process(controlindex)
# end of onselection function
def setupcomboboxes(mode):
# Fill combobox lists
global controls
unittypeindex = icunittype
if mode == 0:
# set up unittype list.
units = []
#print 'conversiondata:', conversiondata
for item in conversiondata:
units.append(item[0])
#print 'item', item, ', units len =', len(units)
controls[unittypeindex]['values'] = units
controls[unittypeindex].current(0) # set the
current item selection
unittype = controls[unittypeindex].current()
convs = conversiondata[unittype][1]
units = []
for u in convs:
units.append(u[0])
cur = 0
for cindex in (icoldunit, icnewunit):
controls[cindex]['values'] = units
controls[cindex].current(cur)
cur += 1
# end of setupcomboboxes function
# Run
root.mainloop()
Exercises:
-- Add a new conversion, say from acres to square feet, modifying the 'conv' tables
accordingly.
-- Add time units: hours, minutes, seconds, days, years.
-- A difficult one: add currency conversions, such as dollars to euros. Why is this
difficult?
T h e M o d e l- V ie w - C o n t r o lle r o r g a n iz a t io n
We'll now study the converter rewritten in object-oriented language. At the same
time, we'll recast it into the Model-View-Controller (MVC) organization, not
changing the basic program but expressing it in the structure underlying most
contemporary object-oriented programs that create viewports or windows for a
Graphical User Interface (GUI). This organization separates the program into
three parts: a Model, a View, and a Controller. The Controller sets up timers and
actions to be performed when the user clicks a mouse button on a control, or
moves or resizes the viewport, that is, all the user interactions. But it shouldn't
need any information about what's actually being done. The View part handles
all the complicated details of painting, moving, and resizing the viewport; we need
only tell it what we want to display-- but not what we want to use it for. Together,
they constitute the GUI. The Model part, on the other hand, is what distinguishes
a particular application from others; it performs calculations and tells the View
what to draw, without needing any information about how the parameters
specifying the model's action were generated.
A major advantage of the MVC organization is that the Controller can often be
reused with many different applications with minimal changes to the Controller;
only the Model needs to be changed.
Note that MVC organization is not rigidly defined, and it can, but does not need to,
be used for programs written in any OOP programming language.
The full listing follows this discussion. There are several things to
note about it. Most obvious is the rearrangement of the function
definitions: the statement
class Control
begins the definition of the Control class, consisting of all the
methods at the subsequent level of indentation. They are
def __init__(self): creates the Entry, Comboboxes, and Label widgets,
binds them to the key actions that call them, sets default values for parameters,
and creates the layout. Note that the two underscores are required by Python;
def onkeypress(self, event): handles key presses in the Entry.
def onselection(self, event): handles changes in the Combobox
selections.
Each method has an added first argument called self, which identifies the class
that issued the callback. The initializer method __init__(self) is called by the
system before any other actions, which ensures that the program will create any
required controls and give them reasonable initial values. These methods are
called constructors in standard OOP terminology. Also, every reference to
objects and methods must define the class instance to which they belong. Thus,
the non-MVC statement
entry.bind('<Key>', onkeypress)
is replaced in the MVC version by
self.entry.bind('<Key>', self.onkeypress)
And the non-MVC statement
cbox[0]['values'] = units
is replaced by
self.cbox[0]['values'] = cv.units
where cv is defined below as a pointer to the instance of the Convert class.
class Controller:
class Converter:
class Length:
conversions = (('cms', 1.0, 0.), ('meters', 1.0e2,
0.), ('kilometers', 1.0e5, 0.),
('inches', 2.54, 0.), ('feet', 30.48, 0.), ('yards',
91.44, 0.),
('miles', 1.609344e5, 0.))
class Mass:
conversions = (('grams', 1.0, 0.), ('kilograms',
1.0e3, 0.),
('ounces', 28.34952, 0.), ('pounds', 453.59237, 0.),
('tons', 9.07185e5, 0.))
class Temperature:
conversions = (('Celsius', 1.0, 0.), ('Fahrenheit',
100./180, -160./9),
('Kelvin', 1.0, -274.15))
# Run
root.mainloop()
Note that the classes Mass, Length, Force, ... are actually subclasses of the
Converter class, and that they don't have explicit initializers; their function here is
like that of structs in other OOP languages.
Time permitting, it's interesting to compare this Python OOP version with a
functionally identical Java program which also exposes its OOP construction and
uses the MVC organization. Download Converter.java to your desktop, run it (if
you have Java installed, which most Macs do; for Windows, you'll have to install it
yourself) by opening a new terminal window (Terminal > Shell > New Window)
and executing the instructions
javac Converter.java
java Converter
and note that it operates nearly identically. Now open both converterOM.py and
Converter.java in text editor windows and compare the way the two languages do
the same thing, in particular the initializer in Python vs. the Constructor in Java,
and the way the Comboboxes, Entry, and Labels are declared and connected to
triggering events.
Questions: should the subclasses have more functionality? For example, should
each of them be able to convert between items in its list, which is now done by the
convert function? Wouldn't this increase the amount of code?
Can you find any "magic cookies"-- unidentified constants in the code? When is it
difficult to avoid them?
Sign in | Recent Site Activity | Report Abus e | Print Page | P owered By Google Sites