You are on page 1of 42

PROGRAMMING SERIES SPECIAL EDITION

PROGRAM
IN PYTHON
Volume Two
Full Circle Magazine is neither affiliated, with nor endorsed by, Canonical Ltd.
Full Circle Magazine Specials
About Full Circle
Full Circle is a free, Find Us
independent, magazine Website:
dedicated to the Ubuntu http://www.fullcirclemagazine.org/
family of Linux operating
systems. Each month, it Forums:
contains helpful how-to http://ubuntuforums.org/
articles and reader- forumdisplay.php?f=270
submitted stories. Welcome to another 'single-topic special' IRC: #fullcirclemagazine on
Full Circle also features a chat.freenode.net
In response to reader requests, we are assembling the
companion podcast, the Full content of some of our serialised articles into dedicated Editorial Team
Circle Podcast which covers
editions. Editor: Ronnie Tucker
the magazine, along with
other news of interest. For now, this is a straight reprint of the series (aka: RonnieTucker)
'Programming in Python', Parts 9-16 from issues #35 ronnie@fullcirclemagazine.org
through #42; nothing fancy, just the facts. Webmaster: Rob Kerfia
Please note: this Special (aka: admin / linuxgeekery-
Edition is provided with Please bear in mind the original publication date; current admin@fullcirclemagazine.org
absolutely no warranty versions of hardware and software may differ from those
whatsoever; neither the illustrated, so check your hardware and software versions Podcaster: Robin Catling
contributors nor Full Circle (aka RobinCatling)
Magazine accept any before attempting to emulate the tutorials in these special podcast@fullcirclemagazine.org
responsibility or liability for editions. You may have later versions of software installed
loss or damage resulting from or available in your distributions' repositories. Communications Manager:
readers choosing to apply this Robert Clipsham
content to theirs or others Enjoy! (aka: mrmonday) -
computers and equipment. mrmonday@fullcirclemagazine.org

The articles contained in this magazine are released under the Creative Commons Attribution-Share Alike 3.0
Unported license. This means you can adapt, copy, distribute and transmit the articles but only under the following conditions:
You must attribute the work to the original author in some way (at least a name, email or URL) and to this magazine by name ('full circle magazine') and
the URL www.fullcirclemagazine.org (but not attribute the article(s) in any way that suggests that they endorse you or your use of the work). If you alter,
transform, or build upon this work, you must distribute the resulting work under the same, similar or a compatible license.
Full Circle Magazine is entirely independent of Canonical, the sponsor of Ubuntu projects and the views and opinions in the magazine should in no
way be assumed to have Canonical endorsement.

Full Circle Magazine


HOW-TO Program In Python - Part 9
remembering what I have and had a song with the name In the early days, we would
FCM#27-34 - Python Parts 1 - 8 where it is. “Clowns (The Demise of the open the file in binary mode,
European Circus with No and dig around getting the
In this and the next Thanks to Fellini)”, you only information as we needed it,
installment we will look at got the first 30 characters. but that was a lot of work,
making a catalog for our MP3 That was a BIG frustration for because there were no
files. We will also take a look many people. So, the standard libraries available to
at some new python concepts “standard” ID3 tag became handle it. Now we have a
as well as re-visiting our known as ID3v1 and a new number of libraries that handle
database skills. format was created called, this for us. We will use one for
Dev Graphics Internet M/media System amazingly enough, ID3v2. our project called Mutagen.
First, an MP3 file can hold This new format allowed for You will want to go into
information about the file variable length information Synaptic and install python-
itself. The title of the song, the and was placed at the mutagen. If you want, you
CD/DVD HDD USB Drive Laptop Wireless album, artist and more beginning of the file, while the could do a search for “ID3” in
information. This information old ID3v1 metadata was still Synaptic. You'll find there are
is held in ID3 tags and is stuck at the end of the file for over 90 packages (in Karmic),

I
referred to as metadata. Back the benefit of the older and if you type “Python” in the
f you are anything like me, in the early days, there was players. Now the metadata quick search box, you'll find 8
you have some of your only a limited amount of container could hold up to 256 packages. There are pros and
favorite music on your information that could be held MB of data. This was ideal for cons with any of them, but for
computer in the form of inside of the MP3 file. radio stations and crazies like our project, we'll stick with
MP3 files. When you have less Originally, it was stored at the me. Under ID3v2, each group Mutagen. Feel free to dig into
than 1000 music files, it's very end of the file in a block of information is held in what's some of the other ones for
rather easy to remember what of 128 bytes. Because of the called a frame and each frame your extended learning.
you have and where it is. I, on small size of this block, you has a frame identifier. In an
the other hand, have many could only hold 30 characters earlier version of ID3v2, the Now that you have Mutagen
more than that. In a past life, I for the title of the song, name identifier was three characters installed, we'll start our coding.
was a DJ and converted most of of the artist, and so on. For long. The current version
my music a number of years many music files, this was fine, (ID3v2.4) uses a four character Start a new project and
ago. The biggest problem that I but (and this is one of my identifier. name it “mCat”. We'll start by
had was disk space. Now the favorite songs ever) when you doing our imports.
biggest problem is
full circle magazine #35 contents ^
PROGRAM IN PYTHON - PART 8
What the heck is that? This as a standalone app. Notice python we want to output, or
from mutagen.mp3 import MP3 is a trick that allows our file to we use '\n' to force a new line stream, to the standard output
be used as either a stand alone and '\t' to force a tab. We also device, usually the terminal
import os application or a re-usable use a '%s' to include the that we are running in. To do
module that gets imported into application name which is held this we use (invisibly) stdout.
from os.path import
join,getsize,exists another app. Basically it says in the sys.argv[0]. We then When we want to send an
“IF this file is the main app, we use the error routine to output error message, we use the
import sys should go into the main routine the message, then exit the stderr stream. This is also the
to run, otherwise we are going application (sys.exit(1)). terminal. So we redirect the
import apsw to use this as a utility module print output to the stderr
and the functions will be called Next, let's flesh out the error stream.
For the most part, you've directly from another program. routine. Here is the full error
seen these before. Next, we routine. Now, let's work on the main
want to create our stubbed Next, we'll flesh out the routine. Here we will setup our
function headers. usage function. Below is the def error(message): connection and cursor for our
print >> sys.stderr,
full code for the usage routine. str(message)
database, then look at our
def MakeDataBase():
pass system argument parameters,
def S2HMS(t): Here we are going to create and if everything is good, we'll
We are using something
pass a message to display to the call our functions to do the
def WalkThePath(musicpath): called redirection here (the
user if they don't start our actual work we want done.
pass “>>”). When we use the
application with a parameter Here's the code:
def error(message): function “print”, we are telling
pass that we need to be able to run
def main():
pass
def usage(): def usage():
pass message = (
'==============================================\n'
'mCat - Finds all *.mp3 files in a given folder (and sub-folders),\n'
Ahhh...something new. We '\tread the id3 tags, and write that information to a SQLite database.\n\n'
now have a main function and 'Usage:\n'
a usage function. What are '\t{0} <foldername>\n'
'\t WHERE <foldername> is the path to your MP3 files.\n\n'
these for? Let's put one more 'Author: Greg Walters\n'
thing in before we discuss 'For Full Circle Magazine\n'
them. '==============================================\n'
).format(sys.argv[0])
if __name__ == '__main__': error(message)
main() sys.exit(1)

full circle magazine #35 contents ^


PROGRAM IN PYTHON - PART 8
you have a path with a space many files we've dealt with.
def main(): in it, for example, Next we we step through each
global connection (/mnt/musicmain/Adult of the files. We clear the local
global cursor Contemporary), the characters variables that hold the
#---------------------------------------------- after the space will be seen as information about each song.
if len(sys.argv) != 2:
usage() another parameter. So, We use the join function from
else: whenever you use a path with os.path to create a proper path
StartFolder = sys.argv[1] a space, make sure you quote and filename so we can tell
if not exists(StartFolder): # From os.path it. We then setup our mutagen where to find the file.
print('Path {0} does not seem to
exist...Exiting.').format(StartFolder) connection and cursor, create Now we pass the filename to
sys.exit(1) the database, then do the the MP3 class getting back an
else: actual hard work in the instance of “audio”. Next we
print('About to work {0} WalkThePath routine and finally get all the ID3 tags this file
folder(s):').format(StartFolder)
# Create the connection and cursor. close our cursor and contains and then step through
connection=apsw.Connection("mCat.db3") connection to the database that list checking for the tags
cursor=connection.cursor() and then tell the user we are we want to deal with and
# Make the database if it doesn't exist... done. The full WalkThePath assigning them to our
MakeDataBase()
# Do the actual work... routine can be found at: temporary variables. This way,
WalkThePath(StartFolder) http://pastebin.com/CegsAXjW. we can keep errors to a
# Close the cursor and connection... minimum. Take a look at the
cursor.close() First we clear the three portion of code dealing with
connection.close()
# Let us know we are finished... counters we will be using to the track number. When
print("FINISHED!") keep track of the work that has mutagen returns a track
been done. Next we open a number it can be a single
file to hold our error log just in value, a value like “4/18” or as
As we did last time, we automatic and secondly the case we have any problems. _trk[0] and _trk[1] or it can be
create two global variables path to our MP3 files. If we Next we do a recursive walk absolutely nothing. We use the
called connection and cursor don't see two parameters, we down the path provided by the try/except wrappers to catch
for our database. Next we look jump to the usage routine, user. Basically, we start at the any errors that will occur due
at the parameters (if any) which prints our message to provided file path and “walk” in to this. Next, look at the
passed from the command line the screen and exits. If we do, and out of any sub-folders that writing of the data records. We
in the terminal. We do this we fall into the else clause of happen to be there, looking for are doing things a bit different
with the sys.argv command. our IF statement. Next, we put any files that have a “.mp3” from last time. Here we create
Here we are looking for two the parameter for the starting extension. Next we increment the SQL statement like before,
parameters, first the path into the StartFolder the folder counter then the file but this time we are replacing
application name which is variable. Understand that if counter to keep track of how the value variables with “?”.
full circle magazine #35 contents ^
PROGRAM IN PYTHON - PART 8
We then put in the values in mutagen and convert it to a
the cursor.execute statement. string using either MY STORY QUICKIE
According to the ASPW web “Hour:Minutes:Seconds” format
site, this is the better way to or “Minutes:Seconds” format. My studio is fully digital with four Windows XP machines in a peer to
peer network. My fifth machine runs Linux Ubuntu 9.04 exclusively
deal with it, so I won't argue Look at the return statements.
as my test machine for Linux. I started with Ubuntu 7.04 and have
with them. Finally we deal with Once again, we are using the upgraded each time there was a release. I have found it to be very
any other types of errors we Python 3.x formatting syntax. stable, easy to use and configure as each version improves the OS.
come up with. For the most However, there's something
part, these will be TypeErrors or new in the mix. We are using At this time it is only my test bed but is linked to my network and
ValueErrors and will probably three substitution sets (0, 1 shares data with my Windows machines. I have been very happy
occur because of Unicode and 2), but what's the “:02n” with the stability of Ubuntu in its upgrades, programs, hardware
characters that can't be after numbers 1 and 2? That support, and driver updates. Although it is unfortunate that more
major vendors such as Adobe don't port over, but Wine seems to
handled. Take a quick look at says that we want leading work well. There are graphics programs and professional printers
the strange way we are zeros to two places. So if a related to my camera equipment that do not work so I will have to
formatting and outputting the song is 2 minutes and 4 wait until Wine gets better or the software gets ported over.
string. We aren't using the '%' seconds, the returned string
substitution character. We are would be “2:04”, not “2:4”. Audio, video, CD/DVD, USB, and Zip drives all seem to work 'out of
using a “{0}” type the box' which is nice. Still some flaws in the software but they
substitution, which is part of The full code of our program appear to be minor annoyances.
the Python 3.x specification. is at:
All in all Ubuntu has been visually refreshing and fun to play with. I
The basic form is: http://pastebin.com/rFf4Gm7E. am not a geek so I really do not use the command line unless
curious about a tutorial and want to try it, the OS GUI is quite
Print('String that will be Dig around on the web and complete for us non-geeks who want to stick to a GUI.
printed with {0} number of
statements”).format(replaceme
see what you can find about
nt values) Mutagen. It does more than I download Full Circle Magazine every month and have shared it
just MP3s. with one of my colleagues to show him what is available. A lot of
people still do not know about the OS and how easy it is to use, but
We are using the basic as the Microsoft disgruntled get the word out I expect to see more
syntax for the efile.writelines growth. The one thing I absolutely love about this OS is the ability to
as well. shut down a misbehaving program. The break button works slickly
is owner of
,a in Linux and eliminates the frustration of waiting for Windows to
Finally we should take a look consulting company in Aurora, unfreeze in XP. Why can't Windows do something as easy as that? I
at the S2HMS routine. This Colorado, and has been seldom need to use the button in Linux anyway which shows how
programming since 1972. He stable Linux is.
routine will take the length of
enjoys cooking, hiking, music,
the song which is a floating and spending time with his -
point value returned by family.

full circle magazine #35 contents ^


HOW-TO Program In Python - Part 10
So... let's talk about XML. data</node3sub1> with a number or punctuation.
XML stands for EXtensible </node3> You should avoid “-”, “.” and
FCM#27-35 - Python Parts 1 - 9 </root>
Markup Language, very much “:” in your tag names since
like HTML. It was designed to some software applications
The first thing to notice is
provide a way to store and might consider them some
the indentation. In reality,
transport data efficiently over sort of command or property
indentation is simply for
the Internet or other of an object. Also, colons are
human consumption. The XML
communication path. XML is reserved for something else.
file would work just as well if it
basically a text file that is Tags are referred to as
looked like this...
formatted using your own tags elements.
Dev Graphics Internet M/media System and should be fairly self- <root><node1>Data
documenting. Being a text Here</node1><node2 Every XML file is basically a
file, it can be compressed to attribute=”something”>Node 2 tree - starting from a root and
allow for faster and easier data</node2><node3><node3sub1 branching out from there.
>more
CD/DVD HDD USB Drive Laptop Wireless transfer of the data. Unlike data</node3sub1></node3></roo Every XML file MUST have a
HTML, XML doesn't do t> root element, which is the
anything by itself. It doesn't parent of everything else in

Y
care how you want your data Next, the tags contained in the file. Look again at our
ou probably have heard to look. As I said a moment the “<>” brackets have some example. After the root, there
of the term XML. You before, XML doesn't require rules. First, they must be a are three child elements:
may not, however, you to stick to a series of single word. Next, when you node1, node2 and node3.
know what it is. XML standard tags. You can create have a start tag (for example While they are children of the
will be the focus of our lesson your own. <root>) you must have a root element, node3 is also a
this month. The goal is: matching closing tag. The parent of node3sub1.
Let's take a look at a closing tag starts with a “/”.
• To familiarize you with what generic example of an XML file: Tags are also case sensitive: Now take a look at node2.
XML is. <node>, <Node>, <NODE> Notice that in addition to
• To show you how to read and <root> and <NodE> are all different having its normal data inside
write XML files in your own <node1>Data Here</node1>
<node2 tags, and the closing tag must the brackets, it also has
applications. match. Tag names may contain something called an attribute.
attribute=”something”>Node 2
• Get you ready for a fairly data</node2> letters, numbers and other These days, many developers
large XML project next time. <node3> characters, but may not start avoid attributes, since
<node3sub1>more
full circle magazine #36 contents ^
PROGRAM IN PYTHON - PART 10
elements are just as effective correct. In fact, some website import
and less hassle, but you will applications use XML files as (http://effbot.org/downloads/#el elementtree.ElementTree as ET
find that attributes are still simple database structures. ementtree) and download the tree =
used. We'll look at them some Now, writing an application to source file directly ET.parse('xmlsample1.xml')
more in a little bit. read this XML file could be (elementtree-1.2.6-
done without too much trouble. 20050316.tar.gz). Once ET.dump(tree)
Let's take a look at the Simply open the file, read each downloaded, I used the
useful example below. line and, based on the package manager to extract it When we run the test
element, deal with the data as to a temporary folder. I program, we should get back
Here we have the root it's read and then close the file changed to that folder and did something like what is shown
element named "people", when you are done. However, a “sudo python setup.py below right.
containing two child elements there are better ways to do it. install”. This placed the files
named "person". Each 'person' into the python common folder All that we did was allow
child has 6 child elements: In the following examples, so I could use it in either ElementTree to open the file,
firstname, lastname, gender, we are going to use a library python 2.5 or 2.6. parse the file into its base
address, city and state. At first module called ElementTree. Now we can start
glance, you might think of this You can get it directly from to work. Create a /usr/bin/python -u
XML file as a database Synaptic by installing python- folder to hold this "/home/greg/Documents/articles/xml/rea
(remembering the last few elementtree. However, I chose month's code, der1.py"
lessons), and you would be to go to the ElementTree copy the above <people>
XML data into your <person>
<people> favorite text <firstname>Samantha</firstname>
<person> <lastname>Pharoh</lastname>
<firstname>Samantha</firstname> editor, and save it
<gender>Female</gender>
<lastname>Pharoh</lastname> into that folder as <address>123 Main St.</address>
<gender>Female</gender> “xmlsample1.xml”. <city>Denver</city>
<address>123 Main St.</address> <state>Colorado</state>
<city>Denver</city> </person>
<state>Colorado</state> Now for our
<person>
</person> code. The first <firstname>Steve</firstname>
<person> thing we want to <lastname>Levon</lastname>
<firstname>Steve</firstname> do is test our <gender>Male</gender>
<lastname>Levon</lastname> <address>332120 Arapahoe
<gender>Male</gender> install of
Blvd.</address>
<address>332120 Arapahoe Blvd.</address> ElementTree. <city>Denver</city>
<city>Denver</city> Here's the code: <state>Colorado</state>
<state>Colorado</state> </person>
</person> </people>
</people>

full circle magazine #36 contents ^


PROGRAM IN PYTHON - PART 10
parts, and dump it out as it is Element: gender - Data: Male broken down within person. coordinates into our GPS and
in memory. Nothing fancy here. Element: address - Data: Next we created a simple for then try to go find it. According
332120 Arapahoe Blvd.
Element: city - Data: Denver loop to walk through each to Wikipedia, there are over
Now, replace your code with Element: state - Data: person object. We then created 1,000,000 active cache sites
the following: Colorado another for loop to pull out the world wide, so there are
data for each person, and probably a few in your area. I
import Now we have each piece of display it by showing the use two websites to get the
elementtree.ElementTree as ET data along with the tag name. element name (.tag) and the locations we search for. One is
tree = We can simply do some pretty data (.text). http://www.geocaching.com/
ET.parse('xmlsample1.xml') printing to deal with what we and the other is
have. Let's look at what we did Now for a more real-world http://navicache.com/. There
person = here. We had ElementTree
tree.findall('.//person')
example. My family and I enjoy are others, but these two are
parse the file into an object an activity called Geocaching. about the biggest.
for p in person: named tree. We then asked If you don't know what that is,
for dat in p: ElementTree to find all it's a “geeky” treasure hunt Files that contain the
print "Element: %s - instances of person. In the
Data: %s" %(dat.tag,dat.text)
that uses a hand-held GPS information for each
sample we are using, there are device to find something geocaching site are usually
two, but it could be 1 or 1000. someone else has hidden. They basic XML files. There are
and run it again. Now your
Person is a child of people and post the gross GPS coordinates applications that will take
output should be:
we know that people is simply on a web site, sometimes with those data and transfer them
/usr/bin/python -u the root. All of our data is clues, and we enter the to the GPS device. Some of
"/home/greg/Documents/articl
es/xml/reader1.py"
<?xml version="1.0" encoding="ISO-8859-1"?>
Element: firstname - Data: <loc version="1.0" src="NaviCache">
Samantha <waypoint>
Element: lastname - Data: <name id="N02CAC"><![CDATA[Take Goofy Pictures at Grapevine Lake by g_phillips
Pharoh Open Cache: Unrestricted
Element: gender - Data: Cache Type: Normal
Female Cache Size: Normal
Element: address - Data: 123 Difficulty: 1.5
Main St. Terrain : 2.0]]></name>
Element: city - Data: Denver <coord lat="32.9890166666667" lon="-97.0728833333333" />
Element: state - Data: <type>Geocache</type>
Colorado <link text="Cache Details">http://www.navicache.com/cgi-
Element: firstname - Data: bin/db/displaycache2.pl?CacheID=11436</link>
Steve </waypoint>
Element: lastname - Data: </loc>
Levon Navicache file

full circle magazine #36 contents ^


PROGRAM IN PYTHON - PART 10
them act as database latitude and longitude, the type an attribute. The name is the
programs - that allow you to of cache it is, and a link to the for w1 in w: part after “CDATA” and before
keep track of your activity, web page for more information if w1.tag == "name": the “Open Cache:” part. We will
sometimes with maps. For now, about this cache. The name be chopping up the string into
we'll concentrate on just element is a long string that Since we will be looking at smaller portions that we want.
parsing the download files. has a bunch of information that the 'name' tag first, let's review We can get part of a string by
we can use, but we'll need to the data we will be getting using:
I went to Navicache and parse it ourselves. Now let's back.
found a recent hide in Texas. create a new application to newstring =
<name oldstring[startposition:endpo
The information from the file is read and display this file. Name id="N02CAC"><![CDATA[Take sition]
shown on the previous page. it "readacache.py". Start with Goofy Pictures at Grapevine
the import and parse Lake by g_phillips
So, we can use the code
Copy the data from that box, statements from our previous
Open Cache: Unrestricted below to grab the information
and save it as “Cache.loc”. example. we need.
Before we start coding, let's Cache Type: Normal
examine the cache file. import
elementtree.ElementTree as ET Cache Size: Normal Next we need to grab the id
that's located in the attribute
The first line basically tells tree = ET.parse('Cache.loc') Difficulty: 1.5 of the name tag. We check to
us that this is a validated XML see if there are any attributes
file, so we can safely ignore it. Now we want to get back Terrain : 2.0]]></name>
(which we know there are), like
The next line (that starts with just the data within the this:
“loc”) is our root, and has the waypoint tag. To do this, we This is one really long string.
attributes "version" and "src". use the .find function within The 'id' of the cache is set as
Remember I said earlier that ElementTree. This will be
attributes are used in some returned in the object “w”. # Get text of cache name up to the phrase "Open Cache: "
files. We'll deal with more CacheName = w1.text[:w1.text.find("Open Cache: ")-1]
attributes in this file as we go w = tree.find('.//waypoint') # Get the text between "Open Cache: " and "Cache Type: "
on. Again, the root in this case OpenCache = w1.text[w1.text.find("Open Cache:
")+12:w1.text.find("Cache Type: ")-1]
can be ignored. The next line Next, we want to go through # More of the same
gives us our waypoint child. (A all the data. We'll use a for loop CacheType = w1.text[w1.text.find("Cache Type:
waypoint is a location where, in to do this. Within the loop, we ")+12:w1.text.find("Cache Size: ")-1]
this case, the cache is to be will check the tag to find the CacheSize = w1.text[w1.text.find("Cache Size:
")+12:w1.text.find("Difficulty: ")-1]
found.) Now we get the elements 'name', 'coord', 'type' Difficulty= w1.text[w1.text.find("Difficulty:
important data that we want. and 'link'. Based on which tag ")+12:w1.text.find("Terrain : ")-1]
There is the name of the we get, we'll pull out the Terrain = w1.text[w1.text.find("Terrain : ")+12:]
cache, the coordinates in information to print it later on.
full circle magazine #36 contents ^
PROGRAM IN PYTHON - PART 10
if w1.keys():
for name,value in
w1.items():
if name == 'id':
CacheID = value is owner of
,a
Now, we can deal with the consulting company in Aurora,
Colorado, and has been
other tags for Coordinates, programming since 1972. He
type, and link the code shown enjoys cooking, hiking, music,
below right. Finally, we print and spending time with his
them out to see them using the family.
code at the bottom right. Far
right is the full code.

You've learned elif w1.tag == "coord":


enough now to read if w1.keys():
most XML files. As for name,value in w1.items():
if name == "lat":
always, you can get
Lat = value
the full code for this elif name == "lon":
lesson on my website Lon = value
which is at: elif w1.tag == "type":
GType = w1.text
http://www.thedesignat
elif w1.tag == "link":
edgeek.com. if w1.keys():
for name, value in w1.items():
Next time, we will Info = value
Link = w1.text
utilize our XML
knowledge to get
information from a print "Cache Name: ",CacheName
wonderful weather site print "Cache ID: ",CacheID
and display it in a print "Open Cache: ",OpenCache
terminal. Have fun! print "Cache Type: ",CacheType
print "Cache Size: ",CacheSize
print "Difficulty: ", Difficulty
print "Terrain: ",Terrain
print "Lat: ",Lat
print "Lon: ",Lon
print "GType: ",GType
print "Link: ",Link

full circle magazine #36 contents ^


HOW-TO Program In Python - Part 11
with another program. Think of search box. There is a wealth ndex.xml?query=80013
FCM#27-36 - Python Parts 1 - 10 the libraries we import. Some of information here. Now, let's
of those can be run as stand- jump to the API web page: Replace the 80013 U.S. ZIP
alone applications, but if we http://wiki.wunderground.com/i code with your postal code or
import the application as a ndex.php/API_-_XML if you are outside the U.S. you
library, we can use many of its can try city, country - like
functions in our own program, One of the first things you Paris, France, or London,
and we get to use someone will notice is the API Terms of England.
else's code. In this case, we Service. Please read and follow
will use specially formatted them. They aren't onerous, And the link for the
Dev Graphics Internet M/media System URL addresses to query the and are really simple to abide ForecastXML:
wunderground website for by. The things that are going http://api.wunderground.com/a
information about the weather to be of interest to us are the uto/wui/geo/ForecastXML/index.
- without using a web browser. xml?query=80013
CD/DVD HDD USB Drive Laptop Wireless Some people might say that
an API is like a secret back and calls. Take Again, replace the 80013
door into another program - some time to scan over them. U.S. ZIP code with your postal

L
that the programmer(s) code or city, country.
ast time, I promised you intentionally put there for our I'm going to skip the
that we would use our use. Either way, this is a GeoLookupXML routine, and let Let's start with the current
XML expertise to grab supported extension of one you look at that on your own. information. Paste the address
weather information application for its use in other We will concentrate on two into your favorite browser.
from a website and display it in applications. other commands: You'll see a great deal of
a terminal. Well, that time has WXCurrentObXML (Current information returned. I'll let
come. Sounds intriguing? Well, Conditions) this time, and you decide what's really
read on, my dear padawan. ForecastXML (Forecast) next important to you, but we'll
We will use an API from time. look at a few of the elements.
www.wunderground.com. I hear Fire up your favorite
the question “What's an API” browser, and head to Here's the link for For our example, we'll pay
rising in your throat. API stands www.wunderground.com. Now WXCurrentObXML: attention to the following tags:
for Application Programming enter your postal code or city http://api.wunderground.com/a
Interface. It's really a fancy and state (or country) into the uto/wui/geo/WXCurrentObXML/i
phrase for a way to interface
full circle magazine #37 contents ^
PROGRAM IN PYTHON - PART 11
When we find a tag we are
""" w_currents.py
interested in, we save that text Returns current conditions, forecast and alerts for a
into a variable that we can use given zipcode from WeatherUnderground.com.
to output the data later on. Usage: python wonderground.py [options]
Options:
Once we have all our data, we
-h, --help Show this help
display it. Fairly simple in -l, --location City,State to use
concept. -z, --zip Zipcode to use as location
Of course, you can add
Examples:
other tags that are of interest Start by naming your file
w_currents.py -h (shows this help information)
to you. However, these tags w_currents.py. Here's the w_currents.py -z 80013 (uses the zip code 80013 as
will provide enough of an import portion of our code: location)
example to take you as far as """
you would like to go. from xml.etree import
ElementTree as ET
Now that we know what we import urllib class CurrentInfo:
will be looking for, let's start """
import sys This routine retrieves the current condition xml data
coding our app. Let's look at
from WeatherUnderground.com
the gross flow of the program. import getopt based off of the zip code or Airport Code...
currently tested only with Zip Code and Airport code
First, we check what the For location,
Next, we'll put a series of if zip code use something like 80013 (no quotes)
user has asked us to do. If she
help lines (above right) above if airport use something like "KDEN" (use double-quotes)
passed a location, we will use if city/state (US) use something like "Aurora,%20CO" or
the imports.
that, otherwise we will use the “Aurora,CO” (use double-quotes)
default location we code into if city/country, use something like "London,%20England"
Be sure to use the triple (use double-quotes)
the main routine. We then pass
double-quotes. This allows us """
that getCurrents routine. We def getCurrents(self,debuglevel,Location):
to have a multi-line comment.
use the location to build the pass
We'll discuss this part more in
request string to send out to
a bit. def output(self):
the web. We use urllib.urlopen
pass
to get the response from the def DoIt(self,Location):
Now we'll create our class
web, and put that in an object, pass
stubs, below right, and the
and pass that object to
main routines, which are shown #=========================================
ElementTree library function
on the following page. # END OF CLASS CurrentInfo()
parse. We then close the #=========================================
connection to the web and
start looking for our tags. You will remember from
full circle magazine #37 contents ^
PROGRAM IN PYTHON - PART 11
previous articles the "if can do by now. def usage():
__name__" line. If we are print __doc__
calling this as a stand alone Finally, we create an def main(argv):
app, we will run the main instance of our CurrentInfo location = 80013
try:
routine - otherwise we can use class that we call currents, and opts, args = getopt.getopt(argv, "hz:l:", ["help=",
this as part of a library. Once in then pass the location to the "zip=", "location="])
the main routine, we then "DoIt" routine. Let's fill that in except getopt.GetoptError:
check what was passed into now: usage()
sys.exit(2)
the routine, if anything. for opt, arg in opts:
def DoIt(self,Location): if opt in ("-h", "--help"):
If the user uses the "-h" or "-- self.getCurrents(1,Location)
usage()
help" parameter, we print out sys.exit()
elif opt in ("-l", "--location"):
the triple-commented help self.output() location = arg
lines at the top of the program elif opt in ("-z", "--zip"):
code. This is called by the Very simple. We pass the location = arg
usage routine telling the app to location and debug level to the print "Location = %s" % location
currents = CurrentInfo()
print __doc__. getCurrents routine, and then currents.DoIt(location)
call the output routine. While
If the user uses the "-l" we could have simply done the #============================================
(location) or "-z" (zipcode), that output directly from the # Main loop
#============================================
will override the internally set getCurrents routine, we are if __name__ == "__main__":
location value. When passing a developing the flexibility to
location, be sure that you use output in various ways if we main(sys.argv[1:])
double quotes to enclose the need to.
string and that you do not use
spaces. For example, to get the The code for the getCurrents early code. If, when you are all
wrapper to make sure that if
current conditions for Dallas, routine is displayed on the next happy with the way your code
something goes wrong, the app
Texas, use -l "Dallas,Texas". page. is working, you can remove
doesn't just blow up. Under the
anything related to debuglevel.
try side, we set up the URL,
Astute readers will realize Here we have a parameter If you are going to release this
then set a timeout of eight
that the -z and -l checks are called debuglevel. By doing into the wild, like if you are
seconds
pretty much the same. You can this, we can print out helpful doing this for someone else, be
(urllib.socket.setdefaulttimeout(
modify the -l to check for information if things don't sure to remove the code and
8)). We do this because,
spaces and reformat the string seem to be going quite the way test it again before release.
sometimes, wunderground is
before passing it to the we want them to. It's also busy and doesn't respond. This
routines. That's something you useful when we are doing our Now, we use a try/except
full circle magazine #37 contents ^
PROGRAM IN PYTHON - PART 11
way we don't just sit there case is "Aurora, CO". That's
waiting for the web. If you want what we want to use as our
def getCurrents(self,debuglevel,Location):
to get more information on location. Next, we are looking if debuglevel > 0:
urllib, a good place to start is for "observation_time". This is print "Location = %s" % Location
http://docs.python.org/library/ur the time when the current try:
CurrentConditions =
llib.html. conditions were recorded. We
'http://api.wunderground.com/auto/wui/geo/WXCurrentObXML
continue looking for all the /index.xml?query=%s' % Location
If anything unexpected data we are interested in - urllib.socket.setdefaulttimeout(8)
happens, we fall through to the using the same methodology. usock = urllib.urlopen(CurrentConditions)
tree = ET.parse(usock)
except section, and print an
usock.close()
error message, and then exit Finally we deal with our except:
the application (sys.exit(2)). output routine which is shown print 'ERROR - Current Conditions - Could not get
top left on the following page. information from server...'
if debuglevel > 0:
Assuming everything works,
print Location
we start looking for our tags. Here we simply print out the sys.exit(2)
The first thing we do is find our variables. # Get Display Location
location with the for loc in tree.findall("//full"):
self.location = loc.text
tree.findall("//full"). Remember, That's all there is to it. A
# Get Observation time
tree is the parsed object sample output from my zip for tim in tree.findall("//observation_time"):
returned by elementtree. What code with debuglevel set to 1 self.obtime = tim.text
is returned by the website API is shown bottom left on the # Get Current conditions
for weather in tree.findall("//weather"):
in part is shown below. next page.
self.we = weather.text
# Get Temp
This is our first instance of Please note that I chose to for TempF in tree.findall("//temperature_string"):
the tag <full>, which in this use the tags that included both self.tmpB = TempF.text
#Get Humidity
<display_location> for hum in tree.findall("//relative_humidity"):
<full>Aurora, CO</full> self.relhum = hum.text
<city>Aurora</city> # Get Wind info
<state>CO</state> for windstring in tree.findall("//wind_string"):
<state_name>Colorado</state_name> self.winds = windstring.text
<country>US</country> # Get Barometric Pressure
<country_iso3166>US</country_iso3166> for pressure in tree.findall("//pressure_string"):
<zip>80013</zip> self.baroB = pressure.text
<latitude>39.65906525</latitude>
<longitude>-104.78105927</longitude>
<elevation>1706.00000000 ft</elevation>
</display_location> getCurrents routine

full circle magazine #37 contents ^


PROGRAM IN PYTHON - PART 11

def output(self):
print 'Weather Information From Wunderground.com'
print 'Weather info for %s ' % self.location
print self.obtime
print 'Current Weather - %s' % self.we
print 'Current Temp - %s' % self.tmpB
print 'Barometric Pressure - %s' % self.baroB
print 'Relative Humidity - %s' % self.relhum Full Circle
Podcast
print 'Winds %s' % self.winds

Fahrenheit and Celsius values.


If you wish, for example, to
display only Celsius values,
you can use the <temp_c> tag
rather than the The is back and better than
<temperature_string> tag. ever!

The full code can be Topics in episode six include:


downloaded from: is owner of • News - Ubuntu 10.04 released
,a • Opinions
http://pastebin.com/4ibJGm74 consulting company in Aurora,
Colorado, and has been
• Gaming - Steam coming to Linux?
Next time, we'll concentrate programming since 1972. He • Feedback
on the forecast portion of the enjoys cooking, hiking, music, ...and all the usual hilarity.
and spending time with his
API. In the meantime, have fun! family.

Location = 80013
Weather Information From Wunderground.com
Weather info for Aurora, Colorado The podcast and show notes are at:
Last Updated on May 3, 11:55 AM MDT
Current Weather - Partly Cloudy http://fullcirclemagazine.org/
Current Temp - 57 F (14 C)
Barometric Pressure - 29.92 in (1013 mb)
Relative Humidity - 25%
Winds From the WNW at 10 MPH
Script terminated.

full circle magazine #37 contents ^


HOW-TO Program In Python - Part 12
Just as there was a web Shown below is a very small when the forecast was
FCM#27-37 - Python Parts 1 - 11 address for the current portion of the txt_forecast set released. The <number> tag
conditions, there is one for the for my area. shows how many forecasts
forecast. Here is the link to the there are for the next 24 hour
forecast XML page: After the txt_forecast parent period. I can't think of a time
http://api.wunderground.com/a element, we get the date, a that I've seen this value less
uto/wui/geo/ForecastXML/index. “number” element, then an than 2. For each forecast for
xml?query=80013 element that has children of its the 24 hour period
own called forecastday which (<forecastday>), you have a
As before, you can change includes period, icon, icons, period number, multiple icon
Dev Graphics Internet M/media System the '80013' to your title and something called options, a title option (“Today”,
City/Country, City/State, or fcttext...then it repeats itself. “Tonight”, “Tomorrow”), and
postal code. You'll probably get The first thing you'll notice is the text of a simple forecast.
back about 600 lines of XML that under txt_forecast, the This is a quick overview of the
CD/DVD HDD USB Drive Laptop Wireless code. You have a root element date isn't a date, but a time forecast, usually for the next
of 'forecast', and then four sub value. It turns out that this is 12 hours.
elements: 'termsofservice',

I
'txt_forecast', 'simpleforecast' <txt_forecast>
n our last session, we and 'moon_phase'. We will <date>3:31 PM MDT</date>
looked at the API from <number>2</number>
concentrate on the −<forecastday>
wunderground, and wrote 'txt_forecast' and <period>1</period>
some code to grab the 'simpleforecast' elements. <icon>nt_cloudy</icon>
current conditions. This time, +<icons></icons>
we will be dealing with the <title>Tonight</title>
Since we went over the −<fcttext>
forecast portion of the API. If usage, main, and “if __name__” Mostly cloudy with a 20
you haven't had a chance to sections last time, I'll leave percent chance of thunderstorms in the evening...then
look at the last two installments those to you to deal with and partly cloudy after midnight. Lows in the mid 40s.
about XML, and the last one Southeast winds 10 to 15 mph shifting to the south after
just concentrate on the midnight.
specifically, you might want to goodies that we need for this </fcttext>
review them before moving time. Since I showed you a </forecastday>
forward. snippet f txt_forecast, let's +<forecastday></forecastday>
</txt_forecast>
start with that.

full circle magazine #38 contents ^


PROGRAM IN PYTHON - PART 12
Before we start working with Now we need to start our
<simpleforecast>
our code, we should take a look class. We will create an __init__ −<forecastday>
at the <simpleforecast> routine to setup and clear the <period>1</period>
portion of the xml file which is variables that we need, this is −<date>
shown right. shown top right on the <epoch>1275706825</epoch>
<pretty_short>9:00 PM MDT</pretty_short>
following page. <pretty>9:00 PM MDT on June 04, 2010</pretty>
There is a <forecastday> <day>4</day>
tag for each day of the forecast If you don't care about <month>6</month>
period, usually 6 days including carrying the ability of both <year>2010</year>
<yday>154</yday>
the current day. You have the Fahrenheit and Celsius, then <hour>21</hour>
date information in various leave out whichever variable <min>00</min>
formats (I personally like the set you don't want. I decided to <sec>25</sec>
<pretty> tag), projected high carry both. <isdst>1</isdst>
<monthname>June</monthname>
and low temps in both <weekday_short/>
Fahrenheit and Celsius, gross Next, we'll start our main <weekday>Friday</weekday>
condition projection, various retrieval routine to get the <ampm>PM</ampm>
icons, a sky icon (sky forecast data. This is shown <tz_short>MDT</tz_short>
<tz_long>America/Denver</tz_long>
conditions at the reporting bottom right on the next page. </date>
station), and “pop” which −<high>
stands for “Probability Of This is pretty much the <fahrenheit>92</fahrenheit>
Precipitation”. The same as the current conditions <celsius>33</celsius>
</high>
<moon_phase> tag provides routine we worked on last time. −<low>
some interesting information The only major difference (so <fahrenheit>58</fahrenheit>
including sunset, sunrise, and far) is the URL we are using. <celsius>14</celsius>
moon information. Now things change. Since we </low>
<conditions>Partly Cloudy</conditions>
have multiple children that <icon>partlycloudy</icon>
Now we'll get into the code. have the same tag within the +<icons>
Here is the import set: parent, we have to make our <skyicon>partlycloudy</skyicon>
parse calls a bit different. The <pop>10</pop>
from xml.etree import </forecastday>
code is top left on the following ...
ElementTree as ET
page. </simpleforecast>
import urllib
Notice we are using tree.find
import sys this time, and we are using for
import getopt
loops to walk through the data.
It's a shame that Python
full circle magazine #38 contents ^
class ForecastInfo:
def __init__(self):
self.forecastText = [] # Today/tonight forecast
information
#================================= self.Title = [] # Today/tonight
# Get the forecast for today and (if available) self.date = ''
tonight self.icon = [] # Icon to use for conditions
#================================= today/tonight
fcst = tree.find('.//txt_forecast') self.periods = 0
for f in fcst: self.period = 0
if f.tag == 'number': #==============================================
self.periods = f.text # Extended forecast information
elif f.tag == 'date': #==============================================
self.date = f.text self.extIcon = [] # Icon to use for extended
for subelement in f: forecast
if subelement.tag == 'period': self.extDay = [] # Day text for this forecast
self.period=int(subelement.text) ("Monday", "Tuesday" etc)
if subelement.tag == 'fcttext': self.extHigh = [] # High Temp. (F)
self.forecastText.append(subelement.text) self.extHighC = [] # High Temp. (C)
elif subelement.tag == 'icon': self.extLow = [] # Low Temp. (F)
self.icon.append( subelement.text) self.extLowC = [] # Low Temp. (C)
elif subelement.tag == 'title': self.extConditions = [] # Conditions text
self.Title.append(subelement.text) self.extPeriod = [] # Numerical Period
information (counter)
self.extpop = [] # Percent chance Of
Precipitation

def GetForecastData(self,location):
try:
forecastdata = 'http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=%s' % location
urllib.socket.setdefaulttimeout(8)
usock = urllib.urlopen(forecastdata)
tree = ET.parse(usock)
usock.close()
except:
print 'ERROR - Forecast - Could not get information from server...'
sys.exit(2)

full circle magazine #38 contents ^


PROGRAM IN PYTHON - PART 12
doesn't offer a SELECT/CASE time, it will be fairly generic.
command set like other The code for this is shown on #=================================
# Now get the extended forecast
languages. The IF/ELIF routine, the right of the following page. #=================================
however, works well, just a bit fcst = tree.find('.//simpleforecast')
clunkier. Now we'll break down Again, if you don't want to for f in fcst:
the code. We assign the carry both Centigrade and for subelement in f:
if subelement.tag == 'period':
variable fcst to everything Fahrenheit information, then self.extPeriod.append(subelement.text)
within the <txt_forecast> tag. modify the code to show what elif subelement.tag == 'conditions':
This gets all the data for that you want. Finally, we have a self.extConditions.append(subelement.text)
group. We then look for the “DoIt” routine: elif subelement.tag == 'icon':
self.extIcon.append(subelement.text)
tags <date> and <number> - elif subelement.tag == 'pop':
since those are simple “first def self.extpop.append(subelement.text)
DoIt(self,Location,US,Include elif subelement.tag == 'date':
level” tags - and load that data Today,Output):
into our variables. Now things for child in subelement.getchildren():
if child.tag == 'weekday':
get a bit more difficult. Look self.GetForecastData(Loca self.extDay.append(child.text)
back at our xml response tion) elif subelement.tag == 'high':
example. There are two for child in subelement.getchildren():
self.output(US,IncludeTod if child.tag == 'fahrenheit':
instances of <forecastday>. ay,Output) self.extHigh.append(child.text)
Under <forecastday> are sub- if child.tag == 'celsius':
elements that consist of Now we can call the routine self.extHighC.append(child.text)
<period>, <icon>, <icons>, as follows: elif subelement.tag == 'low':
<title> and <fcttext>. We'll for child in subelement.getchildren():
if child.tag == 'fahrenheit':
loop through these, and again forecast = ForecastInfo() self.extLow.append(child.text)
use the IF statement to load if child.tag == 'celsius':
them into our variables. forecast.DoIt('80013',1,0,0) self.extLowC.append(child.text)
# Insert your own postal code
Next we need to look at the
That's about it for this time.
extended forecast data for the
I'll leave the alert data to you,
next X number of days. We are
if you want to go through that. is owner of
basically using the same
,a
methodology to fill our consulting company in Aurora,
Here is the complete
variables; this is shown top Colorado, and has been
running code: programming since 1972. He
right.
http://pastebin.com/wsSXMXQx enjoys cooking, hiking, music,
and spending time with his
Now we need to create our family.
output routine. As we did last
full circle magazine #38 contents ^
PROGRAM IN PYTHON - PART 12

def output(self,US,IncludeToday,Output):
# US takes 0,1 or 2
# 0 = Centigrade
# 1 = Fahrenheit
# 2 = both (if available)

Full Circle
# Now print it all
if Output == 0:
for c in range(int(self.period)):
if c <> 1:

Podcast
print '-------------------------------'
print 'Forecast for %s' %
self.Title[c].lower()
print 'Forecast = %s' %
self.forecastText[c]
print 'ICON=%s' % self.icon[c]
print '-----------------------------------'
print 'Extended Forecast...'
if IncludeToday == 1: The is back and better than
startRange = 0 ever!
else:
startRange = 1
for c in range(startRange,6): Topics in episode eight include:
print self.extDay[c] • News - Maverick development
if US == 0: #Centigrade information • Lubuntu interview
print '\tHigh - %s(C)' % • Gaming - Ed reviews Osmos
self.extHigh[c]
print '\tLow - %s(C)' % self.extLow[c] • Feedback
elif US == 1: #Fahrenheit information ...and all the usual hilarity.
print '\tHigh - %s(F)' %
self.extHigh[c]
print '\tLow - %s(F)' % self.extLow[c]
else: #US == 2 both(if available)
print '\tHigh - %s' % self.extHigh[c]
print '\tLow - %s' % self.extLow[c]
if int(self.extpop[c]) == 0:
print '\tConditions - %s.' %
self.extConditions[c] The podcast and show notes are at:
else: http://fullcirclemagazine.org/
print '\tConditions - %s. %d%% chance
of precipitation.' %
(self.extConditions[c],int(self.extpop[c]))

full circle magazine #38 contents ^


HOW-TO Program In Python - Part 13

T
his month, we talk especially for data input and
#!/usr/bin/env python
about using Curses in display, they used graph paper # CursesExample1
Python. No, we're not to design the screen. Each #-------------------------------
talking about using block on the graph paper was # Curses Programming Sample 1
one character position. When #-------------------------------
Python to say dirty words,
import curses
although you can if you really we deal with our Python myscreen = curses.initscr()
feel the need. We are talking programs that run in a myscreen.border(0)
about using the Curses library terminal, we still deal with a myscreen.addstr(12, 25, "See Curses, See Curses Run!")
24x80 screen. However, that myscreen.refresh()
to do some fancy screen
myscreen.getch()
output. limitation can be easily dealt curses.endwin()
with by proper forethought and
If you are old enough to preparation. So, go out to your
remember the early days of local office supply store and myscreen.border(0) command
computers, you will remember get yourself a few pads of to draw a border around our doesn't get called, your
that, in business, computers graph paper. canvas. This isn't needed, but terminal will be left in a major
were all mainframes - with it makes the screen look nicer. mess. So, make sure that you
dumb terminals (screens and Anyway, let's jump right in We then use the addstr method get this method called before
keyboards) for input and and create our first Curses to “write” some text on our your application ends.
output. You could have many program, shown above right. canvas starting on line 12
terminals connected to one I'll explain after you've had a position 25. Think of the Save this program as
computer. The problem was look at the code. .addstr method of a Curses CursesExample1.py and run it
that the terminals were very print statement. Finally, the in a terminal. Some things to
dumb devices. They had Short but simple. Let's .refresh() method makes our note. Whenever you use a
neither windows, colors, or examine it line by line. First, we work visible. If we don't refresh border, it takes up one of our
much of anything - just 24 lines do our imports, which you are the screen, our changes won't “usable” character positions
of 80 characters (at best). very familiar with by now. Next, be seen. Then we wait for the for each character in the
When personal computers we create a new Curses screen user to press any key (.getch) border. In addition, both the
became popular, in the old object, initialize it, and call the and then we release the screen line and character position
days of DOS and CPM, that is object myscreen. (myscreen = object (.endwin) to allow our count is ZERO based. This
what you had as well. When curses.initscr()). This is our terminal to act normally. The means that the first line in our
programmers worked on fancy canvas that we will paint to. curses.endwin() command is screen is line 0 and the last line
screens (those days), Next, we use the VERY important, and, if it is line 23. So, the very top left

full circle magazine #39 contents ^


PROGRAM IN PYTHON - PART 13
position is referred to 0,0 and terminal simply scrolled up #!/usr/bin/env python
the bottom right position is when we printed # CursesExample2
23,79. Let's make a quick something. This time we'll import curses
example (above right) to show take that idea and make a #==========================================================
# MAIN LOOP
this. dummy menu that you can #==========================================================
use to pretty up the try:
Very simple stuff except the cookbook. Shown below is myscreen = curses.initscr()
try/finally blocks. Remember, I what we used back then. myscreen.clear()
myscreen.addstr(0,0,"0 1 2 3
said that curses.endwin is VERY 4 5 6 7")
important and needs to be This time, we'll use myscreen.addstr(1,0,"123456789012345678901234567890123456
called before your application Curses. Start with the 78901234567890123456789012345678901234567890")
finishes. Well, this way, even if following template. You myscreen.addstr(10,0,"10")
myscreen.addstr(20,0,"20")
things go very badly, the might want to save this myscreen.addstr(23,0, "23 - Press Any Key to Continue")
endwin routine will get called. snippet (below right) so myscreen.refresh()
There's many ways of doing you can use it for your own myscreen.getch()
this, but this way seems pretty future programs. finally:
curses.endwin()
simple to me.
Now, save your
Now let's create a nice template again as #!/usr/bin/env python
#-------------------------------
menu system. If you remember “cursesmenu1.py” so that # Curses Programming Template
back a while, we did a we can work on the file #-------------------------------
cookbook application that had and keep the template. import curses
a menu (Programming Python -
def InitScreen(Border):
Part 8). Everything in the if Border == 1:
myscreen.border(0)
===================================================
RECIPE DATABASE #==========================================================
=================================================== # MAIN LOOP
1 - Show All Recipes #==========================================================
2 - Search for a recipe myscreen = curses.initscr()
3 - Show a Recipe InitScreen(1)
4 - Delete a recipe try:
5 - Add a recipe myscreen.refresh()
6 - Print a recipe # Your Code Stuff Here...
0 - Exit myscreen.addstr(1,1, "Press Any Key to Continue")
=================================================== myscreen.getch()
Enter a selection -> finally:
curses.endwin()

full circle magazine #39 contents ^


PROGRAM IN PYTHON - PART 13
Before we go any further
with our code, we are going to curses.initscreen
do this in a modular way. Here LogicLoop
(above right) is a pseudo-code ShowMainMenu # Show the main menu
MainInKey # This is our main input handling routine
example of what we are going While Key != 0:
to do. If Key == 1:
ShowAllRecipesMenu # Show the All Recipes Menu
Of course this pseudo code Inkey1 # Do the input routines for this
ShowMainMenu # Show the main menu
is just that...pseudo. But it If Key == 2:
gives you an idea of where we SearchForARecipeMenu # Show the Search for a Recipe Menu
are going with this whole thing. InKey2 # Do the input routines for this option
Since this is just an example, ShowMainMenu # Show the main menu again
If Key == 3:
we'll only go just so far here, ShowARecipeMenu # Show the Show a recipe menu routine
but you can take it all the way InKey3 # Do the input routine for this routine
if you want. Let's start with the ShowMainMenu # Show the main menu again
main loop (middle far right). … # And so on and so on
curses.endwin() # Restore the terminal

Not much in the way of


programming here. We have def DoMainMenu():
our try|finally blocks just as we myscreen.erase()
myscreen.addstr(1,1, # MAIN LOOP
had in our template. We try:
"========================================")
initialize the Curses screen and myscreen.addstr(2,1, " Recipe myscreen = curses.initscr()
then call a routine named Database") LogicLoop()
LogicLoop. That code is shown myscreen.addstr(3,1, finally:
"========================================") curses.endwin()
bottom far right.
myscreen.addstr(4,1, " 1 - Show All
Recipes")
Again, not much, but this is myscreen.addstr(5,1, " 2 - Search for a
only a sample. Here we are recipe")
going to call two routines. One myscreen.addstr(6,1, " 3 - Show a recipe")
myscreen.addstr(7,1, " 4 - Delete a recipe")
called DoMainMenu and the myscreen.addstr(8,1, " 5 - Add a recipe")
other MainInKey. DoMainMenu myscreen.addstr(9,1, " 6 - Print a recipe")
myscreen.addstr(10,1, " 0 - Exit") def LogicLoop():
will show our main menu, and DoMainMenu()
the MainInKey routine handles myscreen.addstr(11,1,
"========================================") MainInKey()
everything for that main menu. myscreen.addstr(12,1, " Enter a selection: ")
Tthe DoMainMenu routine is myscreen.refresh()
shown right.
full circle magazine #39 contents ^
PROGRAM IN PYTHON - PART 13
Notice that this routine does entered by the user equals 0.
nothing but clear the screen Within the loop, we check to def SearchForARecipeMenu():
myscreen.addstr(4,1, "-------------------------------")
(myscreen.erase), and then see if it's equal to various myscreen.addstr(5,1, " Search in")
print what we want on the values, and, if so, we do a myscreen.addstr(6,1, "-------------------------------")
myscreen.addstr(7,1, " 1 - Recipe Name")
screen. There is nothing here series of routines, and finally myscreen.addstr(8,1, " 2 - Recipe Source")
dealing with keyboard call the main menu when we myscreen.addstr(9,1, " 3 - Ingredients")
myscreen.addstr(10,1," 0 - Exit")
handling. That's the job of the are done. You can fill in most of myscreen.addstr(11,1,"Enter Search Type -> ")
MainInKey routine, which is these routines for yourself by myscreen.refresh()
shown below. now, but we will look at option def InKey2():
2, Search for a Recipe. The key = 'X'
doloop = 1
This is really a simple menu is short and sweet. The while doloop == 1:
routine. We jump into a while InKey2 routine (right) is a bit key = myscreen.getch(11,22)
myscreen.addch(11,22,key)
loop until the key that is more complicated. tmpstr = "Enter text to search in "
if key == ord('1'):
sstr = "'Recipe Name' for -> "
tmpstr = tmpstr + sstr
def MainInKey(): retstring = GetSearchLine(13,1,tmpstr)
key = 'X' break
while key != ord('0'): elif key == ord('2'):
key = myscreen.getch(12,22) sstr = "'Recipe Source' for -> "
myscreen.addch(12,22,key) tmpstr = tmpstr + sstr
retstring = GetSearchLine(13,1,tmpstr)
if key == ord('1'): break
ShowAllRecipesMenu() elif key == ord('3'):
DoMainMenu() sstr = "'Ingredients' for -> "
elif key == ord('2'): tmpstr = tmpstr + sstr
SearchForARecipeMenu() retstring = GetSearchLine(13,1,tmpstr)
break
InKey2() else:
DoMainMenu() retstring = ""
elif key == ord('3'): break
ShowARecipeMenu() if retstring != "":
DoMainMenu() myscreen.addstr(15,1,"You entered - " + retstring)
else:
elif key == ord('4'): myscreen.addstr(15,1,"You entered a blank string")
NotReady("'Delete A Recipe'") myscreen.refresh()
DoMainMenu() myscreen.addstr(20,1,"Press a key")
elif key == ord('5'): myscreen.getch()
NotReady("'Add A Recipe'")
def GetSearchLine(row,col,strng):
DoMainMenu() myscreen.addstr(row,col,strng)
elif key == ord('6'): myscreen.refresh()
NotReady("'Print A Recipe'") instring = myscreen.getstr(row,len(strng)+1)
DoMainMenu() myscreen.addstr(row,len(strng)+1,instring)
myscreen.refresh() myscreen.refresh()
return instring

full circle magazine #39 contents ^


PROGRAM IN PYTHON - PART 13
Again, we are using a http://docs.python.org/library/c
standard while loop here. We urses.html.
set the variable doloop = 1, so
that our loop is endless until
we get what we want. We use
the break command to drop
out of the while loop. The three
options are very similar. The
major difference is that we
OOPS!
Full Circle
start with a variable named
tmpstr, and then append
whatever option text has been It seems that the code for
Podcast
selected...making it a bit more isn't properly
friendly. We then call a routine indented on Pastebin. The
called GetSearchLine to get correct URL for Python Pt.11
the string to search for. We use code is: The is back and better than
the getstr routine to get a http://pastebin.com/Pk74fLF3 ever!
string from the user rather
than a character. We then Please check: Topics in episode ten include:
return that string back to our http://fullcirclemagazine.past • News
input routine for further ebin.com for all Python (and • Opinion - Contributing articles with the FCM Editor.
processing. future) code. • Interview - with Amber Graner
• Feedback
The full code is at: ...and all the usual hilarity.
http://pastebin.com/ELuZ3T4P

One final thing. If you are


interested in looking into
Curses programming further, •
is owner of
there are many other methods ,a
available than what we used The podcast and show notes are at:
consulting company in Aurora,
this month. Besides doing a Colorado, and has been http://fullcirclemagazine.org/
Google search, your best programming since 1972. He
enjoys cooking, hiking, music,
starting point is the official and spending time with his
docs page at family.

full circle magazine #39 contents ^


curses.init_pair([pairnumber] import curses
,[foreground try:
color],[background color]) myscreen = curses.initscr()
curses.start_color()
curses.init_pair(1, curses.COLOR_BLACK,
curses.COLOR_GREEN)
curses.init_pair(2, curses.COLOR_BLUE,
curses.COLOR_WHITE)
curses.init_pair(3,
curses.COLOR_MAGENTA,curses.COLOR_BLACK)
myscreen.clear()
myscreen.addstr(3,1," This is a test
",curses.color_pair(1))
myscreen.addstr(4,1," This is a test
",curses.color_pair(2))
myscreen.addstr(5,1," This is a test
",curses.color_pair(3))
myscreen.refresh()
myscreen.getch()
finally:
curses.endwin()

myscreen.addstr([row],[column
],[text],curses.color_pair(X)
)

contents ^
import curses
def main(stdscreen):
curses.init_pair(1, curses.COLOR_BLACK,
curses.COLOR_GREEN)
curses.init_pair(2, curses.COLOR_BLUE,
curses.COLOR_WHITE)
curses.init_pair(3,
curses.COLOR_MAGENTA,curses.COLOR_BLACK)
stdscreen.clear()
stdscreen.addstr(3,1," This is a test
",curses.color_pair(1))
stdscreen.addstr(4,1," This is a test
",curses.color_pair(2))
stdscreen.addstr(5,1," This is a test
",curses.color_pair(3))
stdscreen.refresh()
stdscreen.getch()
curses.wrapper(main)

import curses
import random

class Game1():
def __init__(self):
pass
def main(self,stdscr):
curses.init_pair(1, curses.COLOR_BLACK,
curses.COLOR_GREEN)
curses.init_pair(2, curses.COLOR_BLUE,
curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_YELLOW,
curses.COLOR_BLUE)
curses.init_pair(4, curses.COLOR_GREEN,
curses.COLOR_BLUE)
curses.init_pair(5, curses.COLOR_BLACK,
curses.COLOR_RED)

def StartUp(self):
curses.wrapper(self.main)
g = Game1()
g.StartUp()

contents ^
# Line Specific Stuff
self.GunLine = 22 #Row where our gun lives
self.GunPosition = 39 #Where the gun starts on GunLine
self.LetterLine = 2 #Where our letter runs right to left
self.ScoreLine = 1 #Where we are going to display the score
self.ScorePosition = 50 #Where the score column is
self.LivesPosition = 65 #Where the lives column is

# Letter Specific Stuff


self.CurrentLetter = "A" #A dummy Holder Variable
self.CurrentLetterPosition = 78 #Where the letter will start on the LetterLine
self.DropPosition = 10 #A dummy Holder Variable
self.DroppingLetter = 0 #Flag - Is the letter dropping?
self.CurrentLetterLine = 3 #A dummy Holder Variable
self.LetterWaitCount = 15 #How many times should we loop before actually
working?

# Bullet Specific Stuff


self.Shooting = 0 #Flag - Is the gun shooting?
self.BulletRow = self.GunLine - 1
self.BulletColumn = self.GunPosition

# Other Stuff
self.LoopCount = 0 #How many loops have we done in MoveLetter
self.GameScore = 0 #Current Game Score
self.Lives = 3 #Default number of lives
self.CurrentColor = 1 #A dummy Holder Variable
self.DecScoreOnMiss = 0 #Set to 1 if you want to decrement the
#score every time the letter hits the
#bottom row

def
CheckKeys(self,scrn,keyin):
pass
def CheckForHit(self,scrn):
pass

contents ^
IF we have waited the correct number of loops THEN
Reset the loop counter
IF we are moving to the left of the screen THEN
Delete the character at the the current row,column.
Sleep for 50 milliseconds
IF the current column is greater than 2 THEN
Decrement the current column
Set the character at the current row,column
IF the current column is at the random column to drop to the bottom THEN
Set the DroppingLetter flag to 1
ELSE
Delete the character at the current row,column
Sleep for 50 milliseconds
IF the current row is less than the line the gun is on THEN
Increment the current row
Set the character at the current row,column
ELSE
IF
Explode (which includes decrementing the score if you wish) and check to
see if we continue.
Pick a new letter and position and start everything over again.
ELSE
Increment the loopcounter
Refresh the screen.

def Explode(self,scrn):
pass
def ResetForNew(self):
self.CurrentLetterLine = self.LetterLine
self.CurrentLetterPosition = 78
self.DroppingLetter = 0
self.PickALetter()
self.PickDropPoint()

def PickALetter(self):
random.seed()
char = random.randint(65,90)
self.CurrentLetter = chr(char)

def PickDropPoint(self):
random.seed()
self.DropPosition = random.randint(3,78)

contents ^
def GameLoop(self,scrn):
test = 1 #Set the loop
while test == 1:
curses.napms(20)
self.MoveLetter(scrn)
keyin =
scrn.getch(self.ScoreLine,self.ScorePosition)
if keyin == ord('Q') or keyin == 27: # 'Q'
or <Esc>
break
else:
self.CheckKeys(scrn,keyin)
self.PrintScore(scrn)
if self.Lives == 0:
break
curses.flushinp()
scrn.clear()

def NewGame(self,scrn):
self.GunChar = curses.ACS_SSBS
scrn.addch(self.GunLine,self.GunPosition,self.Gun
Char,curses.color_pair(2) | curses.A_BOLD)
stdscr.addstr(11,28,"Welcome to Letter Attack") scrn.nodelay(1) #Don't wait for a
stdscr.addstr(13,28,"Press a key to begin....") keystroke...just cache it.
stdscr.getch() self.ResetForNew()
stdscr.clear() self.GameScore = 0
PlayLoop = 1 self.Lives = 3
while PlayLoop == 1: self.PrintScore(scrn)
self.NewGame(stdscr) scrn.move(self.ScoreLine,self.ScorePosition)
self.GameLoop(stdscr)
stdscr.nodelay(0)
curses.flushinp()
stdscr.addstr(12,35,"Game Over")
stdscr.addstr(14,23,"Do you want to play
again? (Y/N)") def PrintScore(self,scrn):
keyin = stdscr.getch(14,56) scrn.addstr(self.ScoreLine,self.ScorePosition,"S
if keyin == ord("N") or keyin == ord("n"): CORE: %d" % self.GameScore)
break scrn.addstr(self.ScoreLine,self.LivesPosition,"L
else: IVES: %d" % self.Lives)
stdscr.clear()

contents ^
def MoveGun(self,scrn,direction):
scrn.addch(self.GunLine,self.GunPosition," ")
if direction == 0: # left
if self.GunPosition > 0:
self.GunPosition -= 1
elif direction == 1: # right
if self.GunPosition < 79:
self.GunPosition += 1
scrn.addch(self.GunLine,self.GunPosition,self.Gun
Char,curses.color_pair(2) | curses.A_BOLD)

if keyin == 260: # left arrow - NOT on keypad


self.MoveGun(scrn,0)
curses.flushinp() #Flush out the input buffer for safety.
elif keyin == 261: # right arrow - NOT on keypad
self.MoveGun(scrn,1)
curses.flushinp() #Flush out the input buffer for safety.
elif keyin == 52: # left arrow ON keypad
self.MoveGun(scrn,0)
curses.flushinp() #Flush out the input buffer for safety.
elif keyin == 54: # right arrow ON keypad
self.MoveGun(scrn,1)
curses.flushinp() #Flush out the input buffer for safety.
elif keyin == 32: #space
if self.Shooting == 0:
self.Shooting = 1
self.BulletColumn = self.GunPosition
scrn.addch(self.BulletRow,self.BulletColumn,"|")
curses.flushinp() #Flush out the input buffer for safety.

def MoveBullet(self,scrn):
scrn.addch(self.BulletRow,self.BulletColumn," ")
if self.BulletRow > self.LetterLine:
self.CheckForHit(scrn)
self.BulletRow -= 1
scrn.addch(self.BulletRow,self.BulletColumn,
"|")
else:
self.CheckForHit(scrn)
scrn.addch(self.BulletRow,self.BulletColumn,
" ")
self.BulletRow = self.GunLine - 1
self.Shooting = 0

contents ^
def CheckForHit(self,scrn):
if self.Shooting == 1:
if self.BulletRow == self.CurrentLetterLine:
if self.BulletColumn == self.CurrentLetterPosition:
scrn.addch(self.BulletRow,self.BulletColumn," ")

self.ExplodeBullet(scrn)
self.GameScore +=1
self.ResetForNew()

def ExplodeBullet(self,scrn):
scrn.addch(self.BulletRow,self.BulletColumn,"X",curses.color_pair(5))
scrn.refresh()
curses.napms(200)
scrn.addch(self.BulletRow,self.BulletColumn,"|",curses.color_pair(5))
scrn.refresh()
curses.napms(200)
scrn.addch(self.BulletRow,self.BulletColumn,"-",curses.color_pair(5))
scrn.refresh()
curses.napms(200)
scrn.addch(self.BulletRow,self.BulletColumn,".",curses.color_pair(5))
scrn.refresh()
curses.napms(200)
scrn.addch(self.BulletRow,self.BulletColumn," ",curses.color_pair(5))
scrn.refresh()
curses.napms(200)

scrn.addch(self.CurrentLetterLine,self.CurrentLetterPosition,"X",curses.color_pair(5))
curses.napms(100)
scrn.refresh()
scrn.addch(self.CurrentLetterLine,self.CurrentLetterPosition,"|",curses.color_pair(5))
curses.napms(100)
scrn.refresh()
scrn.addch(self.CurrentLetterLine,self.CurrentLetterPosition,"-",curses.color_pair(5))
curses.napms(100)
scrn.refresh()
scrn.addch(self.CurrentLetterLine,self.CurrentLetterPosition,".",curses.color_pair(5))
curses.napms(100)
scrn.refresh()
scrn.addch(self.CurrentLetterLine,self.CurrentLetterPosition," ")
scrn.addch(self.GunLine,self.GunPosition,self.GunChar,curses.color_pair(2) | curses.A_BOLD)
scrn.refresh()

contents ^
HOW-TO
Written by Greg Walters
Program In Python - Part 15
#This is the Import
import pygame
from pygame.locals import *
import os
# This will make our game window centered in the screen
os.environ['SDL_VIDEO_CENTERED'] = '1'
# Initialize pygame
pygame.init()
#setup the screen
screen = pygame.display.set_mode((800, 600))
# Set the caption (title bar of the window)
pygame.display.set_caption('Pygame Test #1')
# display the screen and wait for an event
doloop = 1
while doloop:
if pygame.event.wait().type in (KEYDOWN,
MOUSEBUTTONDOWN):
break

screen.fill(Background)
pygame.display.update()

import pygame
from pygame.locals import *
import os
Background = 208, 202, 104

Background = 208, 202, 104

FontForeground = 255,255,255
# White

8 contents ^
PROGRAM IN PYTHON - PART 15
# This will make our game window centered in the screen
os.environ['SDL_VIDEO_CENTERED'] = '1'
# Initialize pygame
pygame.init()
# Setup the screen
screen = pygame.display.set_mode((800, 600))
# Set the caption (title bar of the window)
pygame.display.set_caption('Pygame Test #1')
screen.fill(Background)
pygame.display.update()
font =
pygame.font.Font(None,27) # Our Loop
text = font.render('Here is doloop = 1
some text', True, while doloop:
FontForeground, Background) if pygame.event.wait().type in (KEYDOWN,
textrect = text.get_rect() MOUSEBUTTONDOWN):
screen.blit(text,textrect) break
pygame.display.update()

textRect.centerx =
screen.get_rect().centerx
textRect.centery =
screen.get_rect().centery

9 contents ^
PROGRAM IN PYTHON - PART 15
import pygame
import pygame
from pygame.locals import *
from pygame.locals import *
import os
import os
print
Background = 0,255,127
pygame.font.match_font('Couri
os.environ['SDL_VIDEO_CENTERED'] = '1'
er New')
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Pygame Example #4 - Sprite')
screen.fill(Background)

courier =
pygame.font.match_font('Couri
er New')
font =
pygame.font.Font(courier,27)

10 contents ^
PROGRAM IN PYTHON - PART 15

class Sprite(pygame.sprite.Sprite):
def __init__(self, position):
pygame.sprite.Sprite.__init__(self)
# Save a copy of the screen's rectangle
self.screen = pygame.display.get_surface().get_rect()
# Create a variable to store the previous position of the sprite
self.oldsprite = (0, 0, 0, 0)
self.image = pygame.image.load('stick3.png')
self.rect = self.image.get_rect()
self.rect.x = position[0]
self.rect.y = position[1]

def update(self, amount):


# Make a copy of the current rectangle for use in erasing
self.oldsprite = self.rect
# Move the rectangle by the specified amount
self.rect = self.rect.move(amount)
# Check to see if we are off the screen
if self.rect.x < 0:
self.rect.x = 0
elif self.rect.x > (self.screen.width - self.rect.width):
self.rect.x = self.screen.width - self.rect.width
if self.rect.y < 0:
self.rect.y = 0
elif self.rect.y > (self.screen.height - self.rect.height):
self.rect.y = self.screen.height - self.rect.height

11 contents ^
PROGRAM IN PYTHON - PART 15

character = Sprite((screen.get_rect().x, screen.get_rect().y))


screen.blit(character.image, character.rect)

# Create a Surface the size of our character


blank = pygame.Surface((character.rect.width, character.rect.height))
blank.fill(Background)

pygame.display.update()
DoLoop = 1
while DoLoop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# Check for movement
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
character.update([-10, 0])
elif event.key == pygame.K_UP:
character.update([0, -10])
elif event.key == pygame.K_RIGHT:
character.update([10, 0])
elif event.key == pygame.K_DOWN:
character.update([0, 10])
elif event.key == pygame.K_q:
DoLoop = 0

# Erase the old position by putting our blank Surface on it


screen.blit(blank, character.oldsprite)
# Draw the new position
screen.blit(character.image, character.rect)
# Update ONLY the modified areas of the screen
pygame.display.update([character.oldsprite, character.rect])

Greg Walters

12 contents ^
HOW-TO
Written by Greg Walters
Program In Python - Part 16
PRINT >>> print "This is a test"
File "<stdin>", line 1
print "This is a test"
^
SyntaxError: invalid syntax
>>>
Formatting and
print “This is a test” variable substitution

>>> print("Hello {0}. I'm


glad you are here at
{1}".format("Fred","MySite.co
m"))

Hello Fred. I'm glad you are


here at MySite.com

>>>

print(“this is a test”)

>>> months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] Numbers


>>> print "You selected month %s" % months[3]
You selected month Apr
>>> OLD WAY x = 5/2.0

>>> months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] x = 5/2


>>> print("You selected month {0}".format(months[3]))
You selected month Apr
>>> NEW WAY x = 5/2

8 contents ^
PROGRAM IN PYTHON - PART 16

+======================================+
x = 5//2 | Item 1 3.00 |
| Item 2 15.00 |
+--------------------------------------+
INPUT | Total 18.00 |
+======================================+
Script terminated.
Converting older
programs to Python 3.x print TopOrBottom('=',40)
^
SyntaxError: invalid syntax

response = raw_input('Enter
a selection -> ')

File "pprint1.py", line 18

#pprint1.py
Traceback (most recent call #Example of semi-useful functions
last):
def TopOrBottom(character,width):
File "<stdin>", line 1, in # width is total width of returned line
<module> return '%s%s%s' % ('+',(character * (width-2)),'+')
NameError: name 'raw_input' def Fmt(val1,leftbit,val2,rightbit):
is not defined # prints two values padded with spaces
# val1 is thing to print on left, val2 is thing to print on right
# leftbit is width of left portion, rightbit is width of right portion
part2 = '%.2f' % val2
return '%s%s%s%s' % ('| ',val1.ljust(leftbit-2,' '),part2.rjust(rightbit-2,' '),' |')
# Define the prices of each item
item1 = 3.00
item2 = 15.00
response = input('Enter a # Now print everything out...
selection -> ') print TopOrBottom('=',40)
print Fmt('Item 1',30,item1,10)
print Fmt('Item 2',30,item2,10)
print TopOrBottom('-',40)
print Fmt('Total',30,item1+item2,10)
Not Equal print TopOrBottom('=',40)

9 contents ^
PROGRAM IN PYTHON - PART 16

cp pprint1.py pprint1v3.py
Do I switch to 3.x now?

> 2to3 pprint1v3.py > 2to3 -w pprint1v3.py


RefactoringTool: Skipping implicit fixer: buffer RefactoringTool: Skipping implicit fixer: buffer
RefactoringTool: Skipping implicit fixer: idioms RefactoringTool: Skipping implicit fixer: idioms
RefactoringTool: Skipping implicit fixer: set_literal RefactoringTool: Skipping implicit fixer: set_literal
RefactoringTool: Skipping implicit fixer: ws_comma RefactoringTool: Skipping implicit fixer: ws_comma
RefactoringTool: Refactored pprint1v3.py RefactoringTool: Refactored pprint1v3.py
--- pprint1v3.py (original) --- pprint1v3.py (original)
+++ pprint1v3.py (refactored) +++ pprint1v3.py (refactored)
@@ -15,9 +15,9 @@ @@ -15,9 +15,9 @@
item1 = 3.00 item1 = 3.00
item2 = 15.00 item2 = 15.00
# Now print everything out... # Now print everything out...
-print TopOrBottom('=',40) -print TopOrBottom('=',40)
-print Fmt('Item 1',30,item1,10) -print Fmt('Item 1',30,item1,10)
-print Fmt('Item 2',30,item2,10) -print Fmt('Item 2',30,item2,10)
-print TopOrBottom('-',40) -print TopOrBottom('-',40)
-print Fmt('Total',30,item1+item2,10) -print Fmt('Total',30,item1+item2,10)
-print TopOrBottom('=',40) -print TopOrBottom('=',40)
+print(TopOrBottom('=',40)) +print(TopOrBottom('=',40))
+print(Fmt('Item 1',30,item1,10)) +print(Fmt('Item 1',30,item1,10))
+print(Fmt('Item 2',30,item2,10)) +print(Fmt('Item 2',30,item2,10))
+print(TopOrBottom('-',40)) +print(TopOrBottom('-',40))
+print(Fmt('Total',30,item1+item2,10)) +print(Fmt('Total',30,item1+item2,10))
+print(TopOrBottom('=',40)) +print(TopOrBottom('=',40))
RefactoringTool: Files that need to be modified: RefactoringTool: Files that were modified:
RefactoringTool: pprint1v3.py RefactoringTool: pprint1v3.py

10 contents ^
PROGRAM IN PYTHON - PART 16
#pprint1.py
#Example of semi-useful functions

def TopOrBottom(character,width):
# width is total width of returned line
return '%s%s%s' % ('+',(character * (width-2)),'+')
def Fmt(val1,leftbit,val2,rightbit):
# prints two values padded with spaces
# val1 is thing to print on left, val2 is thing to print on right
# leftbit is width of left portion, rightbit is width of right portion
part2 = '%.2f' % val2
return '%s%s%s%s' % ('| ',val1.ljust(leftbit-2,' '),part2.rjust(rightbit-2,' '),' |')
# Define the prices of each item
item1 = 3.00
item2 = 15.00
# Now print everything out...
print(TopOrBottom('=',40))
print(Fmt('Item 1',30,item1,10))
print(Fmt('Item 2',30,item2,10))
print(TopOrBottom('-',40))
print(Fmt('Total',30,item1+item2,10))
print(TopOrBottom('=',40))

Links

Greg Walters

11 contents ^

You might also like