See Test Your Knowledge: Part VII Exercises in Chapter 36 for the exercises.
try/except. My version of the oops function (file oops.py) follows. As for the noncoding questions, changing oops to raise a KeyError instead of an IndexError means that the try handler won’t catch the exception—it “percolates” to the top level and triggers Python’s default error message. The names KeyError and IndexError come from the outermost built-in names scope (the B in “LEGB”). Import builtins in 3.X (and __builtin__ in Python 2.X) and pass it as an argument to the dir function to see this for yourself.
def oops():
raise IndexError()
def doomed():
try:
oops()
except IndexError:
print('caught an index error!')
else:
print('no error caught...')
if __name__ == '__main__': doomed()
% python oops.py
caught an index error!
Exception objects and lists. Here’s the way I extended this module for an exception of my own, file oops2.py:
from __future__ import print_function # 2.X
class MyError(Exception): pass
def oops():
raise MyError('Spam!')
def doomed():
try:
oops()
except IndexError:
print('caught an index error!')
except MyError as data:
print('caught error:', MyError, data)
else:
print('no error caught...')
if __name__ == '__main__':
doomed()
% python oops2.py
caught error: <class '__main__.MyError'> Spam!
Like all class exceptions, the instance is accessible via the as variable data; the error message shows both the class (<...>) and its instance (Spam!). The instance must be inheriting both an __init__ and a __repr__ or __str__ from Python’s Exception class, or it would print much like the class does. See Chapter 35 for details on how this works in built-in exception classes.
Error handling. Here’s one way to solve this one (file exctools.py). I did my tests in a file, rather than interactively, but the results are similar enough for full credit. Notice that the empty except and sys.exc_info approach used here will catch exit-related exceptions that listing Exception with an as variable won’t; that’s probably not ideal in most applications code, but might be useful in a tool like this designed to work as a sort of exceptions firewall.
import sys, traceback
def safe(callee, *pargs, **kargs):
try:
callee(*pargs, **kargs) # Catch everything else
except: # Or "except Exception as E:"
traceback.print_exc()
print('Got %s %s' % (sys.exc_info()[0], sys.exc_info()[1]))
if __name__ == '__main__':
import oops2
safe(oops2.oops)
c:\code> py −3 exctools.py
Traceback (most recent call last):
File "C:\code\exctools.py", line 5, in safe
callee(*pargs, **kargs) # Catch everything else
File "C:\code\oops2.py", line 6, in oops
raise MyError('Spam!')
oops2.MyError: Spam!
Got <class 'oops2.MyError'> Spam!
The following sort of code could turn this into a function decorator that could wrap and catch exceptions raised by any function, using techniques introduced in Chapter 32, but covered more fully in Chapter 39 in the next part of the book—it augments a function, rather than expecting it to be passed in explicitly:
import sys, traceback
def safe(callee):
def callproxy(*pargs, **kargs):
try:
return callee(*pargs, **kargs)
except:
traceback.print_exc()
print('Got %s %s' % (sys.exc_info()[0], sys.exc_info()[1]))
raise
return callproxy
if __name__ == '__main__':
import oops2
@safe
def test():
oops2.oops()
test()
Self-study examples. Here are a few examples for you to study as time allows; for more, see follow-up books—such as Programming Python, from which these examples were borrowed or derived—and the Web:
# Find the largest Python source file in a single directory import os, glob dirname = r'C:\Python33\Lib' allsizes = [] allpy = glob.glob(dirname + os.sep + '*.py') for filename in allpy: filesize = os.path.getsize(filename) allsizes.append((filesize, filename)) allsizes.sort() print(allsizes[:2]) print(allsizes[-2:]) # Find the largest Python source file in an entire directory tree import sys, os, pprint if sys.platform[:3] == 'win': dirname = r'C:\Python33\Lib' else: dirname = '/usr/lib/python' allsizes = [] for (thisDir, subsHere, filesHere) in os.walk(dirname): for filename in filesHere: if filename.endswith('.py'): fullname = os.path.join(thisDir, filename) fullsize = os.path.getsize(fullname) allsizes.append((fullsize, fullname)) allsizes.sort() pprint.pprint(allsizes[:2]) pprint.pprint(allsizes[-2:]) # Find the largest Python source file on the module import search path import sys, os, pprint visited = {} allsizes = [] for srcdir in sys.path: for (thisDir, subsHere, filesHere) in os.walk(srcdir): thisDir = os.path.normpath(thisDir) if thisDir.upper() in visited: continue else: visited[thisDir.upper()] = True for filename in filesHere: if filename.endswith('.py'): pypath = os.path.join(thisDir, filename) try: pysize = os.path.getsize(pypath) except: print('skipping', pypath) allsizes.append((pysize, pypath)) allsizes.sort() pprint.pprint(allsizes[:3]) pprint.pprint(allsizes[-3:]) # Sum columns in a text file separated by commas filename = 'data.txt' sums = {} for line in open(filename): cols = line.split(',') nums = [int(col) for col in cols] for (ix, num) in enumerate(nums): sums[ix] = sums.get(ix, 0) + num for key in sorted(sums): print(key, '=', sums[key]) # Similar to prior, but using lists instead of dictionaries for sums import sys filename = sys.argv[1] numcols = int(sys.argv[2]) totals = [0] * numcols for line in open(filename): cols = line.split(',') nums = [int(x) for x in cols] totals = [(x + y) for (x, y) in zip(totals, nums)] print(totals) # Test for regressions in the output of a set of scripts import os testscripts = [dict(script='test1.py', args=''), # Or glob script/args dir dict(script='test2.py', args='spam')] for testcase in testscripts: commandline = '%(script)s %(args)s' % testcase output = os.popen(commandline).read() result = testcase['script'] + '.result' if not os.path.exists(result): open(result, 'w').write(output) print('Created:', result) else: priorresult = open(result).read() if output != priorresult: print('FAILED:', testcase['script']) print(output) else: print('Passed:', testcase['script']) # Build GUI with tkinter (Tkinter in 2.X) with buttons that change color and grow from tkinter import * # Use Tkinter in 2.X import random fontsize = 25 colors = ['red', 'green', 'blue', 'yellow', 'orange', 'white', 'cyan', 'purple'] def reply(text): print(text) popup = Toplevel() color = random.choice(colors) Label(popup, text='Popup', bg='black', fg=color).pack() L.config(fg=color) def timer(): L.config(fg=random.choice(colors)) win.after(250, timer) def grow(): global fontsize fontsize += 5 L.config(font=('arial', fontsize, 'italic')) win.after(100, grow) win = Tk() L = Label(win, text='Spam', font=('arial', fontsize, 'italic'), fg='yellow', bg='navy', relief=RAISED) L.pack(side=TOP, expand=YES, fill=BOTH) Button(win, text='press', command=(lambda: reply('red'))).pack(side=BOTTOM, fill=X) Button(win, text='timer', command=timer).pack(side=BOTTOM, fill=X) Button(win, text='grow', command=grow).pack(side=BOTTOM, fill=X) win.mainloop() # Similar to prior, but use classes so each window has own state information from tkinter import * import random class MyGui: """ A GUI with buttons that change color and make the label grow """ colors = ['blue', 'green', 'orange', 'red', 'brown', 'yellow'] def __init__(self, parent, title='popup'): parent.title(title) self.growing = False self.fontsize = 10 self.lab = Label(parent, text='Gui1', fg='white', bg='navy') self.lab.pack(expand=YES, fill=BOTH) Button(parent, text='Spam', command=self.reply).pack(side=LEFT) Button(parent, text='Grow', command=self.grow).pack(side=LEFT) Button(parent, text='Stop', command=self.stop).pack(side=LEFT) def reply(self): "change the button's color at random on Spam presses" self.fontsize += 5 color = random.choice(self.colors) self.lab.config(bg=color, font=('courier', self.fontsize, 'bold italic')) def grow(self): "start making the label grow on Grow presses" self.growing = True self.grower() def grower(self): if self.growing: self.fontsize += 5 self.lab.config(font=('courier', self.fontsize, 'bold')) self.lab.after(500, self.grower) def stop(self): "stop the button growing on Stop presses" self.growing = False class MySubGui(MyGui): colors = ['black', 'purple'] # Customize to change color choices MyGui(Tk(), 'main') MyGui(Toplevel()) MySubGui(Toplevel()) mainloop() # Email inbox scanning and maintenance utility """ scan pop email box, fetching just headers, allowing deletions without downloading the complete message """ import poplib, getpass, sys mailserver = 'your pop email server name here' # pop.server.net mailuser = 'your pop email user name here' mailpasswd = getpass.getpass('Password for %s?' % mailserver) print('Connecting...') server = poplib.POP3(mailserver) server.user(mailuser) server.pass_(mailpasswd) try: print(server.getwelcome()) msgCount, mboxSize = server.stat() print('There are', msgCount, 'mail messages, size ', mboxSize) msginfo = server.list() print(msginfo) for i in range(msgCount): msgnum = i+1 msgsize = msginfo[1][i].split()[1] resp, hdrlines, octets = server.top(msgnum, 0) # Get hdrs only print('-'*80) print('[%d: octets=%d, size=%s]' % (msgnum, octets, msgsize)) for line in hdrlines: print(line) if input('Print?') in ['y', 'Y']: for line in server.retr(msgnum)[1]: print(line) # Get whole msg if input('Delete?') in ['y', 'Y']: print('deleting') server.dele(msgnum) # Delete on srvr else: print('skipping') finally: server.quit() # Make sure we unlock mbox input('Bye.') # Keep window up on Windows # CGI server-side script to interact with a web browser #!/usr/bin/python import cgi form = cgi.FieldStorage() # Parse form data print("Content-type: text/html\n") # hdr plus blank line print("<HTML>") print("<title>Reply Page</title>") # HTML reply page print("<BODY>") if not 'user' in form: print("<h1>Who are you?</h1>") else: print("<h1>Hello <i>%s</i>!</h1>" % cgi.escape(form['user'].value)) print("</BODY></HTML>") # Database script to populate a shelve with Python objects # see also Chapter 28 shelve and Chapter 31 pickle examples rec1 = {'name': {'first': 'Bob', 'last': 'Smith'}, 'job': ['dev', 'mgr'], 'age': 40.5} rec2 = {'name': {'first': 'Sue', 'last': 'Jones'}, 'job': ['mgr'], 'age': 35.0} import shelve db = shelve.open('dbfile') db['bob'] = rec1 db['sue'] = rec2 db.close() # Database script to print and update shelve created in prior script import shelve db = shelve.open('dbfile') for key in db: print(key, '=>', db[key]) bob = db['bob'] bob['age'] += 1 db['bob'] = bob db.close() # Database script to populate and query a MySql database from MySQLdb import Connect conn = Connect(host='localhost', user='root', passwd='XXXXXXX') curs = conn.cursor() try: curs.execute('drop database testpeopledb') except: pass # Did not exist curs.execute('create database testpeopledb') curs.execute('use testpeopledb') curs.execute('create table people (name char(30), job char(10), pay int(4))') curs.execute('insert people values (%s, %s, %s)', ('Bob', 'dev', 50000)) curs.execute('insert people values (%s, %s, %s)', ('Sue', 'dev', 60000)) curs.execute('insert people values (%s, %s, %s)', ('Ann', 'mgr', 40000)) curs.execute('select * from people') for row in curs.fetchall(): print(row) curs.execute('select * from people where name = %s', ('Bob',)) print(curs.description) colnames = [desc[0] for desc in curs.description] while True: print('-' * 30) row = curs.fetchone() if not row: break for (name, value) in zip(colnames, row): print('%s => %s' % (name, value)) conn.commit() # Save inserted records # Fetch and open/play a file by FTP import webbrowser, sys from ftplib import FTP # Socket-based FTP tools from getpass import getpass # Hidden password input if sys.version[0] == '2': input = raw_input # 2.X compatibility nonpassive = False # Force active mode FTP for server? filename = input('File?') # File to be downloaded dirname = input('Dir? ') or '.' # Remote directory to fetch from sitename = input('Site?') # FTP site to contact user = input('User?') # Use () for anonymous if not user: userinofo = () else: from getpass import getpass # Hidden password input userinfo = (user, getpass('Pswd?')) print('Connecting...') connection = FTP(sitename) # Connect to FTP site connection.login(*userinfo) # Default is anonymous login connection.cwd(dirname) # Xfer 1k at a time to localfile if nonpassive: # Force active FTP if server requires connection.set_pasv(False) print('Downloading...') localfile = open(filename, 'wb') # Local file to store download connection.retrbinary('RETR ' + filename, localfile.write, 1024) connection.quit() localfile.close() print('Playing...') webbrowser.open(filename)