Appendix A. Answers to the Exercises

Chapter 1

  1. In the Python shell, type the string, "Rock a by baby, on the tree top, when the wind blows the cradle will drop." Feel free to experiment with the number of and escape sequences to see how this affects what gets displayed on your screen. You can even try changing their placement. What do you think you are likely to see?

  2. In the Python shell, use the same string indicated in Exercise 1, but this time, display it using the print() function. Once more, try differing the number of and escape sequences. How do you think it will differ?

Exercise 1 Solution

'Rock a by baby,
	on the tree top,		when the wind blows
			 the
cradle will drop.'

Because this is not being printed, the special characters (those preceded with a backslash) are not translated into a form that will be displayed differently from how you typed them.

Exercise 2 Solution

Rock a by baby,
        on the tree top,                when the wind blows
                         the cradle will drop.

When they are printed, " " and " " produce a newline and a tab character, respectively. When the print() function is used, it will render them into special characters that don't appear on your keyboard, and your screen will display them.

Chapter 2

Do the following first three exercises in Notepad and save the results in a file called ch2_exercises.py. You can run it from within Python by opening the file and choosing Run Module.

  1. In the Python shell, multiply 5 and 10. Try this with other numbers as well.

  2. Print every number from 6 through 14 in base 8.

  3. Print every number from 9 through 19 in base 16.

  4. Try to elicit other errors from the Python interpreter — for instance, by deliberately misspelling print as pinrt. Notice how as you work on a file in the Python shell, it will display print differently than it does pinrt.

Exercise 1 Solution

>>> 5 * 10
50

Exercise 2 Solution

>>> print("%o" % 6)
6
>>> print("%o" % 7)
7
>>> print("%o" % 8)
10
>>> print("%o" % 9)
11
>>> print("%o" % 10)
12
>>> print("%o" % 11)
13
>>> print("%o" % 12)
14
>>> print("%o" % 13)
15
>>> print("%o" % 14)
16

Exercise 3 Solution

>>> print("%x" % 9)
9
>>> print("%x" % 10)
a
>>> print("%x" % 11)
b
>>> print("%x" % 12)
c
>>> print("%x" % 13)
d
>>> print("%x" % 14)
e
>>> print("%x" % 15)
f
>>> print("%x" % 16)
10
>>> print("%x" % 17)
11
>>> print("%x" % 18)
12
>>> print("%x" % 19)
13

Exercise 4 Solution

When an unknown function is called, Python doesn't know that the name that's been typed in is necessarily a function at all, so it just flags a general syntax error:

>>> pintr("%x" & x)
  File "<input>", line 1
    Pintr("%x" & x)

SyntaxError: invalid syntax

You'll notice, however, that Python Shell will display print in bold when you type it. This is because print is a special word to Python, and Python Shell knows this. You can help yourself catch errors by paying attention to how the editor reacts to what you've typed.

Chapter 3

Perform all of the following in the Python shell:

  1. Create a list called dairy_section with four elements from the dairy section of a supermarket.

  2. Print a string with the first and last elements of the dairy_section list.

  3. Create a tuple called milk_expiration with three elements: the month, day, and year of the expiration date on the nearest carton of milk.

  4. Print the values in the milk_expiration tuple in a string that reads "This milk carton will expire on 12/10/2005."

  5. Create an empty dictionary called milk_carton. Add the following key/value pairs. You can make up the values or use a real milk carton:

    • expiration_date: Set it to the milk_expiration tuple.

    • fl_oz: Set it to the size of the milk carton on which you are basing this.

    • Cost: Set this to the cost of the carton of milk.

    • brand_name: Set this to the name of the brand of milk you're using.

  6. Print out the values of all of the elements of the milk_carton using the values in the dictionary, and not, for instance, using the data in the milk_expiration tuple.

  7. Show how to calculate the cost of six cartons of milk based on the cost of milk_carton.

  8. Create a list called cheeses. List all of the cheeses you can think of. Append this list to the dairy_section list, and look at the contents of dairy_section. Then remove the list of cheeses from the array.

  9. How do you count the number of cheeses in the cheese list?

  10. Print out the first five letters of the name of your first cheese.

Exercise 1 Solution

>>> dairy_section = ["milk", "cottage cheese", "butter", "yogurt"]

Exercise 2 Solution

>>> print("First: %s and Last %s" % (dairy_section[0], dairy_section[1]))
First: milk and Last cottage cheese

Exercise 3 Solution

>>> milk_expiration = (10, 10, 2009)

Exercise 4 Solution

>>> print("This milk will expire on %d/%d/%d" % (milk_expiration[0],
milk_expiration[1], milk_expiration[2]))
This milk will expire in 10/10/2009

Exercise 5 Solution

>>> milk_carton = {}
>>> milk_carton["expiration_date"] = milk_expiration
>>> milk_carton["fl_oz"] = 32
>>> milk_carton["cost"] = 1.50
>>> milk_carton["brand_name"] = "Milk"

Exercise 6 Solution

>>> print("The expiration date is %d/%d/%d" %
(milk_carton["expiration_date"][0], milk_carton["expiration_date"][1],
milk_carton["expiration_date"][2]))
The expiration date is 10/10/2009

Exercise 7 Solution

>>> print("The cost for 6 cartons of milk is %.02f" % (6*
milk_carton["cost"]))
The cost for 6 cartons of milk is 9.00

Exercise 8 Solution

>>> cheeses = ["cheddar", "american", "mozzarella"]
>>> dairy_section.append(cheeses)
>>> dairy_section
['milk', 'cottage cheese', 'butter', 'yogurt', ['cheddar', 'american', 'mozzarella']]
>>> dairy_section.pop()
['cheddar', 'american', 'mozzarella']

Exercise 9 Solution

>>> len(dairy_section)
4

Exercise 10 Solution

>>> print("Part of some cheese is %s" % cheeses[0][0:5])
Part of some cheese is chedd

Chapter 4

Perform all of the following in the codeEditor Python shell:

  1. Using a series of if ... : statements, evaluate whether the numbers from 0 through 4 are True or False by creating five separate tests.

  2. Create a test using a single if ... : statement that will tell you whether a value is between 0 and 9 inclusively (that is, the number can be 0 or 9 as well as all of the numbers in between, not just 1–8) and print a message if it's a success. Test it.

  3. Using if ... :, elif, ...: and else:, create a test for whether a value referred to by a name is in the first two elements of a sequence. Use the if ... : to test for the first element of the list; use elif ... : to test the second value referenced in the sequence; and use the else: clause to print a message indicating whether the element being searched for is not in the list.

  4. Create a dictionary containing foods in an imaginary refrigerator, using the name fridge. The name of the food will be the key, and the corresponding value of each food item should be a string that describes the food. Then create a name that refers to a string containing the name of a food. Call the name food_sought. Modify the test from Exercise 3 to be a simple if ... : test (no elif ... : or else: will be needed here) for each key and value in the refrigerator using a for ... in ... : loop to test every key contained in the fridge. If a match is found, print a message that contains the key and the value and then use break to leave the loop. Use an else ... : statement at the end of the for loop to print a message for cases in which the element wasn't found.

  5. Modify Exercise 3 to use a while ... : loop by creating a separate list called fridge_list that will contain the values given by fridge.keys. As well, use a variable named, current_key that will refer to the value of the current element in the loop that will be obtained by the method fridge_list.pop. Remember to place fridge_list.pop as the last line of the while ... : loop so that the repetition will end normally. Use the same else: statement at the end of the while loop as the one used at the end of Exercise 3.

  6. Query the fridge dictionary created in Exercise 3 for a key that is not present, and elicit an error. In cases like this, the KeyError can be used as a shortcut to determining whether or not the value you want is in the list. Modify the solution to Exercise 3 so that instead of using a for ... in ... : a try: block is used.

Exercise 1 Solution

The key theme here is that 0 is False, and everything else is considered not False, which is the same as True:

>>> if 0:
...     print("0 is True")
...
>>> if 1:
...     print("1 is True")
...
1 is True
>>> if 2:
...     print("2 is True")
...
2 is True
>>> if 3:
...     print("3 is True")
...
3 is True
>>> if 4:
...     print("4 is True")
...
4 is True
>>> if 5:
...     print("5 is True")
...
5 is True

Exercise 2 Solution

>>> number = 3
>>> if number >= 0 and number <= 9:
...    print("The number is between 0 and 9: %d" % number)
...
The number is between 0 and 9: 3

Exercise 3 Solution

>>> test_tuple = ("this", "little", "piggie", "went", "to", "market")
>>> search_string = "toes"
>>> if test_tuple[0] == search_string:
...     print("The first element matches")
... elif test_tuple[1] == search_string:
...     print("the second element matches")
... else:
...     print("%s wasn't found in the first two elements" % search_string)
...
toes wasn't found in the first two elements

Exercise 4 Solution

>>> fridge = {"butter":"Dairy spread", "peanut butter":"non-dairy spread", "cola":"fizzy water"}
>>> food_sought = "chicken"
>>> for food_key in fridge.keys():
...     if food_key == food_sought:
...         print("Found what I was looking for: %s is %s" % (food_sought, fridge[food_key]))
...         break
... else:
...     print("%s wasn't found in the fridge" % food_sought)
...
chicken wasn't found in the fridge

Exercise 5 Solution

>>> fridge = {"butter":"Dairy spread", "peanut butter":"non-dairy spread",
"cola":"fizzy water"}
>>> fridge_list = fridge.keys()
>>> current_key = fridge_list.pop()
>>> food_sought = "cola"
>>> while len(fridge_list) > 0:
...     if current_key == food_sought:
...         print("Found what I was looking for: %s is %s" % (food_sought,
fridge[current_key]))
...         break
...     current_key = fridge_list.pop()
... else:
...     print("%s wasn't found in the fridge" % food_sought)
...
Found what I was looking for: cola is fizzy water

Exercise 6 Solution

>>> fridge = {"butter":"Dairy spread", "peanut butter":"non-dairy spread", "cola":"fizzy water"}
>>> food_sought = "chocolate milk"
>>> try:
...     fridge[food_sought]
... except KeyError:
...     print("%s wasn't found in the fridge" % food_sought)
... else:
...     print("Found what I was looking for: %s is %s" % (food_sought,
fridge[food_key]))
...
chocolate milk wasn't found in the fridge

Chapter 5

  1. Write a function called do_plus that accepts two parameters and adds them together with the "+" operation.

  2. Add type checking to confirm that the type of the parameters is either an integer or a string. If the parameters aren't good, raise a TypeError.

  3. This one is a lot of work, so feel free to take it in pieces. In Chapter 4, a loop was written to make an omelet. It did everything from looking up ingredients to removing them from the fridge and making the omelet. Using this loop as a model, alter the make_omelet function by making a function called make_omelet_q3. It should change make_omelet in the following ways to get it to more closely resemble a real kitchen:

    1. The fridge should be passed into the new make_omelet as its first parameter. The fridge's type should be checked to ensure it is a dictionary.

    2. Add a function to check the fridge and subtract the ingredients to be used. Call this function remove_from_fridge. This function should first check to see if enough ingredients are in the fridge to make the omelet, and only after it has checked that should it remove those items to make the omelet. Use the error type LookupError as the type of error to raise.

    3. The items removed from the fridge should be placed into a dictionary and returned by the remove_from_fridge function to be assigned to a name that will be passed to make_food. After all, you don't want to remove food if it's not going to be used.

    4. Rather than a cheese omelet, choose a different default omelet to make. Add the ingredients for this omelet to the get_omelet_ingredients function.

  4. Alter make_omelet to raise a TypeError error in the get_omelet_ingredients function if a salmonella omelet is ordered. Try ordering a salmonella omelet and follow the resulting stack trace.

Exercise 1 Solution

def do_plus(first, second):
    return first + second

Exercise 2 Solution

def do_plus(first, second):
    for param in (first, second):
if (type(param) != type("")) and (type(param) != type(1)):
    raise TypeError("This function needs a string or an integer")
return first + second

Exercise 3 Solution

# Part 1 - fridge has to go before the omelet_type.  omelet_type is an
# optional parameter with a default parameter, so it has to go at the end.
# This can be used with a fridge such as:
# f = {'eggs':12, 'mozzarella cheese':6,
#      'milk':20, 'roast red pepper':4, 'mushrooms':3}
# or other ingredients, as you like.
def make_omelet_q3(fridge, omelet_type = "mozzarella"):
    """This will make an omelet. You can either pass in a dictionary
    that contains all of the ingredients for your omelet, or provide
    a string to select a type of omelet this function already knows
    about
    The default omelet is a mozerella omelet"""

    def get_omelet_ingredients(omelet_name):
        """This contains a dictionary of omelet names that can be produced,
and their ingredients"""
        # All of our omelets need eggs and milk
        ingredients = {"eggs":2, "milk":1}
        if omelet_name == "cheese":
            ingredients["cheddar"] = 2
        elif omelet_name == "western":
            ingredients["jack_cheese"] = 2
            ingredients["ham"]         = 1
            ingredients["pepper"]      = 1
            ingredients["onion"]       = 1
        elif omelet_name == "greek":
            ingredients["feta_cheese"] = 2
            ingredients["spinach"]     = 2
        # Part 5
        elif omelet_name == "mozzarella":
            ingredients["mozzarella cheese"] = 2
            ingredients["roast red pepper"] = 2
            ingredients["mushrooms"] = 1
        else:
            print("That's not on the menu, sorry!")
            return None
        return ingredients
    # part 2 - this version will use the fridge that is available
    # to the make_omelet function.
    def remove_from_fridge(needed):
        recipe_ingredients = {}
        # First check to ensure we have enough
        for ingredient in needed.keys():
            if needed[ingredient] > fridge[ingredient]:
                raise LookupError("not enough %s to continue" % ingredient)
        # Then transfer the ingredients.
        for ingredient in needed.keys():
            # Remove it from the fridge
fridge[ingredient] = fridge[ingredient] - needed[ingredient]
        # and add it to the dictionary that will be returned
        recipe_ingredients[ingredient] = needed[ingredient]
    # Part 3 - recipe_ingredients now has all the needed ingredients
    return recipe_ingredients

# Part 1, continued - check the type of the fridge
if type(fridge) != type({}):
    raise TypeError("The fridge isn't a dictionary!")

if type(omelet_type) == type({}):
    print("omelet_type is a dictionary with ingredients")
    return make_food(omelet_type, "omelet")
elif type(omelet_type) == type(""):
    needed_ingredients = get_omelet_ingredients(omelet_type)
    omelet_ingredients = remove_from_fridge(needed_ingredients)
    return make_food(omelet_ingredients, omelet_type)
else:
    print("I don't think I can make this kind of omelet: %s" %
    omelet_type)

Exercise 4 Solution

The get_omelet_ingredient from make_omelet_q3 could be changed to look like the following:

def get_omelet_ingredients(omelet_name):
    """This contains a dictionary of omelet names that can be produced,and their ingredients"""
        # All of our omelets need eggs and milk
        ingredients = {"eggs":2, "milk":1}
        if omelet_name == "cheese":
            ingredients["cheddar"] = 2
        elif omelet_name == "western":
            ingredients["jack_cheese"] = 2
            ingredients["ham"]         = 1
            ingredients["pepper"]      = 1
            ingredients["onion"]       = 1
        elif omelet_name == "greek":
            ingredients["feta_cheese"] = 2
            ingredients["spinach"]     = 2
        # Part 5
        elif omelet_name == "mozerella":
            ingredients["mozerella cheese"] = 2
            ingredients["roast red pepper"] = 2
            ingredients["mushrooms"] = 1
        # Question 4 - we don' want anyone hurt in our kitchen!
        elif omelet_name == "salmonella":
            raise TypeError("We run a clean kitchen, you won't get this
            here")
        else:
            print("That's not on the menu, sorry!")
            return None
        return ingredients

When run, the error raised by trying to get the salmonella omelet will result in the following error:

>>> make_omelet_q3({'mozzarella cheese':5, 'eggs':5, 'milk':4, 'roast red
pepper':6, 'mushrooms':4}, "salmonella")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "ch5.py", line 209, in make_omelet_q3
    omelet_ingredients = get_omelet_ingredients(omelet_type)
  File "ch5.py", line 179, in get_omelet_ingredients
    raise TypeError, "We run a clean kitchen, you won't get this here"
TypeError: We run a clean kitchen, you won't get this here
>>>

Note that depending on the contents of your ch5.py file, the exact line numbers shown in your stack trace will be different from those shown here.

Next, you can see that line 179 is where get_omelet_ingredients raised the error (though it may be at a different line in your own file).

If you called this from within another function, the stack would be one layer deeper, and you would see the information relating to that extra layer as well.

Chapter 6

Each of the following exercises builds on the exercises that preceded it:

  1. Add an option to the Omelet class's mix method to turn off the creation messages by adding a parameter that defaults to True, indicating that the "mixing ..." messages should be printed.

  2. Create a method in class Omelet that uses the new mix method from Exercise 1. Called quick_cook, it should take three parameters: the kind of omelet, the quantity wanted, and the Fridge that they'll come from. The quick_cook method should do everything required instead of requiring three method calls, but it should use all of the existing methods to accomplish this, including the modified mix method with the mix messages turned off.

  3. For each of the methods in the Omelet class that do not have a docstring, create one. In each docstring, make sure you include the name of the method, the parameters that the method takes, what the method does, what value or values it returns upon success, and what it returns when it encounters an error (or what exceptions it raises, if any).

  4. View the docstrings that you've created by creating an Omelet object.

  5. Create a Recipe class that can be called by the Omelet class to get ingredients. The Recipe class should have the ingredient lists of the same omelets that are already included in the Omelet class. You can include other foods if you like. The Recipe class should include methods to retrieve a recipe, get(recipe_name), a method to add a recipe as well as name it, and create (recipe_name, ingredients), where the ingredients are a dictionary with the same format as the one already used in the Fridge and Omelet classes.

  6. Alter the __init__ method of Omelet so that it accepts a Recipe class. To do this, you can do the following:

    1. Create a name, self.recipe, that each Omelet object will have.

    2. The only part of the Omelet class that stores recipes is the internal method __known_kinds. Alter __known_kinds to use the recipes by calling self.recipe.get() with the kind of omelet that's desired.

    3. Alter the set_new_kind method so that it places the new recipe into self.recipe and then calls set_kind to set the current omelet to the kind just added to the recipe.

    4. In addition, modify __known_kinds to use the recipe method's get method to find out the ingredients of an omelet.

  7. Try using all of the new classes and methods to determine whether you understand them.

Exercise 1 Solution

def mix(self, display_progress = True):
        """
        mix(display_progress = True) - Once the ingredients have been
obtained from a fridge call this
        to prepare the ingredients.  If display_progress is False do not print
messages.
        """
        for ingredient in self.from_fridge.keys():
            if display_progress == True:
                print("Mixing %d %s for the %s omelet" %
(self.from_fridge[ingredient], ingredient, self.kind))
        self.mixed = True

Exercise 2 Solution

Note that you could go one step further and make the quiet setting of the mix function an option, too. As it is, this doesn't give you much feedback about what's going on, so when you test it, it may look a bit strange.

def quick_cook(self, fridge, kind = "cheese", quantity = 1):
    """
    quick_cook(fridge, kind = "cheese", quantity = 1) -
        performs all the cooking steps needed.  Turns out an omelet fast.
    """

    self.set_kind(kind)
    self.get_ingredients(fridge)
    self.mix(False)
    self.make()

Exercise 3 Solution

Just the documentation, not the functions, would look something like this. However, you should find a format that suits you.

Note that only undocumented functions will have their docstrings described here.

class Omelet:
    """This class creates an omelet object.  An omelet can be in one of
    two states: ingredients, or cooked.
    An omelet object has the following interfaces:
    get_kind() - returns a string with the type of omelet
    set_kind(kind) - sets the omelet to be the type named
    set_new_kind(kind, ingredients) - lets you create an omelet
    mix() - gets called after all the ingredients are gathered from the
fridge
    cook() - cooks the omelet
    """
    def __init__(self, kind="cheese"):
        """__init__(self, kind="cheese")
        This initializes the Omelet class to default to a cheese omelet.
        Other methods
        """
        self.set_kind(kind)
        return

    def set_kind(self, kind):
        """
        set_kind(self, kind) - changes the kind of omelet that will be
created
            if the type of omelet requested is not known then return False
        """
    def get_kind(self):
        """
        get_kind() - returns the kind of omelet that this object is making
        """

    def set_kind(self, kind):
        """
        set_kind(self, kind) - changes the kind of omelet that will be created
            if the type of omelet requested is not known then return False
        """

    def set_new_kind(self, name, ingredients):
        """
        set_new_kind(name, ingredients) - create a new type of omelet that is
            called "name" and that has the ingredients listed in "ingredients"
        """

    def __known_kinds(self, kind):
        """
        __known_kinds(kind) - checks for the ingredients of "kind" and returns
them
returns False if the omelet is unknown.
    """

def get_ingredients(self, fridge):
    """
    get_ingredients(fridge) - takes food out of the fridge provided
    """

def mix(self):
    """
    mix() - Once the ingredients have been obtained from a fridge call this
    to prepare the ingredients.
    """

def make(self):
    """
    make() - once the ingredients are mixed, this cooks them
    """

Exercise 4 Solution

>>> print("%s" % o.__doc__)
This class creates an omelet object.  An omelet can be in one of
    two states: ingredients, or cooked.
    An omelet object has the following interfaces:
    get_kind() - returns a string with the type of omelet
    set_kind(kind) - sets the omelet to be the type named
    set_new_kind(kind, ingredients) - lets you create an omelet
    mix() - gets called after all the ingredients are gathered from the fridge
    cook() - cooks the omelet

>>> print("%s" % o.set_new_kind.__doc__)

        set_new_kind(name, ingredients) - create a new type of omelet that is
            called "name" and that has the ingredients listed in "ingredients"

You can display the remaining docstrings in the same way.

Exercise 5 Solution

class Recipe:
    """
    This class houses recipes for use by the Omelet class
    """

    def __init__(self):
        self.set_default_recipes()
        return

    def set_default_recipes(self):
        self.recipes = {"cheese" : {"eggs":2, "milk":1, "cheese":1},
                        "mushroom" : {"eggs":2, "milk":1, "cheese":1,
"mushroom":2},
                        "onion" : {"eggs":2, "milk":1, "cheese":1, "onion":1}}
def get(self, name):
        """
        get(name) - returns a dictionary that contains the ingredients needed
to
        make the omelet in name.
        When name isn't known, returns False
        """
        try:
            recipe = self.recipes[name]
            return recipe
        except KeyError:
            return False

    def create(self, name, ingredients):
         """
         create(name, ingredients) - adds the omelet named "name" with the
ingredients
         "ingredients" which is a dictionary.
         """

         self.recipes[name] = ingredients

Exercise 6 Solution

Note that the order of parameters in the interface for the class has now been changed, because you can't place a required argument after a parameter that has an optional default value.

When you test this, remember that you now create an omelet with a recipe as its mandatory parameter.

def __init__(self, recipes, kind="cheese"):
    """__init__(self, recipes, kind="cheese")
    This initializes the omelet class to default to a cheese omelet.

    """
    self.recipes = recipes
    self.set_kind(kind)

    return

def set_new_kind(self, name, ingredients):
     """
    set_new_kind(name, ingredients) - create a new type of omelet that is
        called "name" and that has the ingredients listed in "ingredients"
    """
    self.recipes.create(name, ingredients)
    self.set_kind(name)
    return
def __known_kinds(self, kind):
    """
    __known_kinds(kind) - checks for the ingredients of "kind" and returns them
        returns False if the omelet is unknown.
    """
    return self.recipes.get(kind)

Chapter 7

Moving code to modules and packages is straightforward and doesn't necessarily require any changes to the code to work, which is part of the ease of using Python.

In these exercises, the focus is on testing your modules, because testing is essentially writing small programs for an automated task.

  1. Write a test for the Foods.Recipe module that creates a recipe object with a list of foods, and then verifies that the keys and values provided are all present and match up. Write the test so that it is run only when Recipe.py is called directly, and not when it is imported.

  2. Write a test for the Foods.Fridge module that will add items to the Fridge, and exercise all of its interfaces except get_ingredients, which requires an Omelet object.

  3. Experiment with these tests. Run them directly from the command line. If you've typed them correctly, no errors should come up. Try introducing errors to elicit error messages from your tests.

Exercise 1 Solution

Remember that you're not a regular user of your class when you write tests. You should feel free to access internal names if you need to!

if __name__ == '__main__':
    r = Recipe()
    if r.recipes != {"cheese" : {"eggs":2, "milk":1, "cheese":1},
                        "mushroom" : {"eggs":2, "milk":1, "cheese":1,
"mushroom":2},
                        "onion" : {"eggs":2, "milk":1, "cheese":1, "onion":1}}:
        Print("Failed: the default recipes is not the correct list")
    cheese_omelet = r.get("cheese")
    if cheese_omelet != {"eggs":2, "milk":1, "cheese":1}:
        print("Failed: the ingredients for a cheese omelet are wrong")
    western_ingredients = {"eggs":2, "milk":1, "cheese":1, "ham":1,
    "peppers":1, "onion":1}
    r.create("western", western_ingredients)
    if r.get("western") != western_ingredients:
        print("Failed to set the ingredients for the western")
    else:
        print("Succeeded in getting the ingredients for the western.")

Exercise 2 Solution

At the end of the Fridge module, insert the following code. Note the comment about changing the add_many function to return True. If you don't do that, add_many will return None, and this test will always fail!

if __name__ == '__main__':
    f = Fridge({"eggs":10, "soda":9, "nutella":2})
    if f.has("eggs") != True:
print("Failed test f.has('eggs')")
    else:
        print("Passed test f.has('eggs')")
    if f.has("eggs", 5) != True:
        print("Failed test f.has('eggs', 5)")
    else:
        print("Passed test f.has('eggs', 5)")
    if f.has_various({"eggs":4, "soda":2, "nutella":1}) != True:
        print('Failed test f.has_various({"eggs":4, "soda":2, "nutella"1})')
    else:
        print('Passed test f.has_various({"eggs":4, "soda":2, "nutella"1})')
    # Check to see that when we add items, that the number of items in the
fridge
    # is increased!
    item_count = f.items["eggs"]
    if f.add_one("eggs") != True:
        print('Failed test f.add_one("eggs")')
    else:
        print('Passed test f.add_one("eggs")')
    if f.items["eggs"] != (item_count + 1):
        print('Failed f.add_one() did not add one')
    else:
        print('Passed f.add_one() added one')
    item_count = {}
    item_count["eggs"] = f.items["eggs"]
    item_count["soda"] = f.items["soda"]
    # Note that the following means you have to change add_many to return True!
    if f.add_many({"eggs":3,"soda":3}) != True:
        print('Failed test f.add_many({"eggs":3,"soda":3})')
    else:
        print('Passed test f.add_many({"eggs":3,"soda":3})')
    if f.items["eggs"] != (item_count["eggs"] + 3):
        print("Failed f.add_many did not add eggs")
    else:
        print("Passed f.add_many added eggs")
    if f.items["soda"] != (item_count["soda"] + 3):
        print("Failed f.add_many did not add soda")
    else:
        print("Passed f.add_many added soda")

    item_count = f.items["eggs"]
    if f.get_one("eggs") != True:
        print('Failed test f.get_one("eggs")')
    else:
        print('Passed test f.get_one("eggs")')
    if f.items["eggs"] != (item_count - 1):
        print("Failed get_one did not remove an eggs")
    else:
        print("Passed get_one removed an eggs")

    item_count = {}
    item_count["eggs"] = f.items["eggs"]
    item_count["soda"] = f.items["soda"]
    eats = f.get_many({"eggs":3, "soda":3})
    if eats["eggs"] != 3 or eats["soda"] != 3:
print('Failed test f.get_many({"eggs":3, "soda":3})')
else:
    print('Passed test f.get_many({"eggs":3, "soda":3})')

if f.items["eggs"] != (item_count["eggs"] - 3):
    print("Failed get many didn't remove eggs")
else:
    print("Passed get many removed eggs")

if f.items["soda"] != (item_count["soda"] - 3):
    print("Failed get many didn't remove soda")
else:
    print("Passed get many removed soda")

Exercise 3 Solution

You can try to generate errors by mistyping the name of a key in one place in the module, and confirming that this results in your tests warning you. If you find situations that these tests don't catch, you should try to code a test for that situation so it can't ever catch you.

Chapter 8

  1. Create another version of the (nonrecursive) print_dir function that lists all subdirectory names first, followed by names of files in the directory. Names of subdirectories should be alphabetized, as should file names. (For extra credit, write your function in such a way that it calls os.listdir only one time. Python can manipulate strings faster than it can execute os.listdir.)

  2. Modify the rotate function to keep only a fixed number of old versions of the file. The number of versions should be specified in an additional parameter. Excess old versions above this number should be deleted.

Exercise 1 Solution

Here's a simple but inefficient way to solve the problem:

import os

def print_dir(dir_path):
    # Loop through directory entries, and print directory names.
    for name in sorted(os.listdir(dir_path)):
        full_path = os.path.join(dir_path, name)
        if os.path.isdir(full_path):
            print(full_path)

    # Loop again, this time printing files.
    for name in sorted(os.listdir(dir_path)):
        full_path = os.path.join(dir_path, name)
if os.path.isfile(full_path):
            print(full_path)

Here's the extra-credit solution, which only scans and sorts the directory once:

import os

def print_dir(dir_path):
    # Loop through directory entries.  Since we sort the combined
    # directory entries first, the subdirectory names and file names
    # will each be sorted, too.
    file_names = []
    for name in sorted(os.listdir(dir_path)):
        full_path = os.path.join(dir_path, name)
        if os.path.isdir(full_path):
            # Print subdirectory names now.
            print(full_path)
        elif os.path.isfile(full_path):
            # Store file names for later.
            file_names.append(full_path)

    # Now print the file names.
    for name in file_names:
        print(name)

Exercise 2 Solution

import os
import shutil

def make_version_path(path, version):
    if version == 0:
        return path
    else:
        return path + "." + str(version)


def rotate(path, max_keep, version=0):
    """Rotate old versions of file 'path'.

    Keep up to 'max_keep' old versions with suffixes .1, .2, etc.
    Larger numbers indicate older versions."""

    src_path = make_version_path(path, version)
    if not os.path.exists(src_path):
        # The file doesn't exist, so there's nothing to do.
        return

    dst_path = make_version_path(path, version + 1)
    if os.path.exists(dst_path):
        # There already is an old version with this number.  What to do?
if version < max_keep - 1:
        # Renumber the old version.
        rotate(path, max_keep, version + 1)
    else:
        # Too many old versions, so remove it.
        os.remove(dst_path)

shutil.move(src_path, dst_path)

Chapter 9

Chapter 9 is a grab-bag of different features. At this point, the best exercise is to test all of the sample code, looking at the output produced and trying to picture how the various ideas introduced here could be used to solve problems that you'd like to solve or would have liked to solve in the past.

Chapter 10

  1. How can you get access to the functionality provided by a module?

  2. How can you control which items from your modules are considered public? (Public items are available to other Python scripts.)

  3. How can you view documentation on a module?

  4. How can you find out what modules are installed on a system?

  5. What kind of Python commands can you place in a module?

Exercise 1 Solution

You get access to the functionality with a module by importing the module or items from the module.

Exercise 2 Solution

If you define the variable __all__, you can list the items that make up the public API for the module. For example:

__all__ = ['Meal','AngryChefException', 'makeBreakfast',
    'makeLunch', 'makeDinner', 'Breakfast', 'Lunch', 'Dinner']

If you do not define the __all__ variable (although you should), the Python interpreter looks for all items with names that do not begin with an underscore.

Exercise 3 Solution

The help function displays help on any module you have imported. The basic syntax follows:

help(module)

Exercise 4 Solution

Look in the directories listed in the variable sys.path for the locations of modules on your system. You need to import the sys module first.

Exercise 5 Solution

Any Python commands can be placed in a module. Your modules can have Python commands, Python functions, Python variables, Python classes, and so on. In most cases, though, you want to avoid running commands in your modules. Instead, the module should define functions and classes and let the caller decide what to invoke.

Chapter 11

  1. Modify the scan_pdf.py script to start at the root, or topmost, directory. On Windows, this should be the topmost directory of the current disk (C:, D:, and so on). Doing this on a network share can be slow, so don't be surprised if your G: drive takes a lot more time when it comes from a file server). On UNIX and Linux, this should be the topmost directory (the root directory, /).

  2. Modify the scan_pdy.py script to match only PDF files with the text boobah in the file name.

  3. Modify the scan_pdf.py script to exclude all files with the text boobah in the file name.

Exercise 1 Solution

import os, os.path
import re

def print_pdf (arg, dir, files):
   for file in files:
      path = os.path.join (dir, file)
      path = os.path.normcase (path)
      if not re.search (r".*.pdf", path): continue
      if re.search (r" ", path): continue

      print(path)

os.path.walk ('/', print_pdf, 0)

Note how this example just changes the name of the directory to start processing with the os.path.walk function.

Exercise 2 Solution

import os, os.path
import re
def print_pdf (arg, dir, files):
   for file in files:
      path = os.path.join (dir, file)
      path = os.path.normcase (path)
      if not re.search (r".*.pdf", path): continue
      if not re.search (r"boobah", path): continue

      print(path)

os.path.walk ('.', print_pdf, 0)

This example just includes an additional test in the print_pdf function.

Exercise 3 Solution

import os, os.path
import re

def print_pdf (arg, dir, files):
   for file in files:
      path = os.path.join (dir, file)
      path = os.path.normcase (path)
      if not re.search (r".*.pdf", path): continue
      if re.search (r"boobah", path): continue

      print(path)

os.path.walk ('.', print_pdf, 0)

Note how this example simply removes the not from the second test.

Chapter 13

  1. Experiment with different layouts using different pack orders.

  2. Practice modifying the look of your widgets by changing every property.

Chapter 14

  1. Suppose you need to write a Python script to store the pizza preferences for the workers in your department. You need to store each person's name along with that person's favorite pizza toppings. Which technologies are most appropriate to implement this script?

    1. Set up a relational database such as MySQL or Sqlite.

    2. Use a dbm module such as dbm.

    3. Implement a web-service-backed rich web application to create a buzzword-compliant application.

  2. Rewrite the following example query using table name aliases:

    select employee.firstname, employee.lastname, department.name
    from employee, department
    where employee.dept =  department.departmentid
    order by employee.lastname desc
  3. The terminate.py script, shown previously, removes an employee row from the employee table; but this script is not complete. There remains a row in the user table for the same person. Modify the terminate.py script to delete both the employee and the user table rows for that user.

Exercise 1 Solution

The choice is c, of course. Just joking. The most appropriate choice is b, with the keys being the person's name and the values holding the pizza ingredients, perhaps using commas to separate the different ingredients.

Exercise 2 Solution

You can use any alias you like. Here is one example:

select e.firstname, e.lastname, d.name
from employee e, department d
where e.dept =  d.departmentid
order by e.lastname desc

Exercise 3 Solution

You don't have to change much. The changes are in bold:

import sys
import sqlite3

conn=sqlite3.connect('sample_database')
cursor = connection.cursor()

employee = sys.argv[1]

# Query to find the employee ID.
query = """
select e.empid
from user u, employee e
where username=? and u.employeeid = e.empid
"""
cursor.execute(query,(employee,));
for row in cursor.fetchone():
    if (row != None):
        empid = row

# Now, modify the employee.
cursor.execute("delete from employee where empid=?", (empid,))
cursor.execute("delete from user where employeeid=?", (empid,))

connection.commit()
cursor.close()
connection.close()

Chapter 15

  1. Given the following configuration file for a Python application, write some code to extract the configuration information using a DOM parser:

    <?xml version="1.0"?>
    <!DOCTYPE config SYSTEM "configfile.dtd">
    <config>
      <utilitydirectory>/usr/bin</utilitydirectory>
      <utility>grep</utility>
      <mode>recursive</mode>
    </config>
  2. Given the following DTD, named configfile.dtd, write a Python script to validate the previous configuration file:

    <!ELEMENT config  (utilitydirectory, utility, mode)>
    <!ELEMENT utilitydirectory    (#PCDATA)*>
    <!ELEMENT utility    (#PCDATA)*>
    <!ELEMENT mode  (#PCDATA)*>
  3. Use SAX to extract configuration information from the preceding config file instead of DOM.

Exercise 1 Solution

from xml.dom.minidom import parse
import xml.dom.minidom

# open an XML file and parse it into a DOM
myDoc = parse('config.xml')
myConfig = myDoc.getElementsByTagName("config")[0]

#Get utility directory
myConfig.getElementsByTagName("utilitydirectory")[0].childNodes[0].data

#Get utility
myConfig.getElementsByTagName("utility")[0].childNodes[0].data

#get mode
myConfig.getElementsByTagName("mode")[0].childNodes[0].data

#.....Do something with data.....

Exercise 2 Solution

#!/usr/bin/python

from xml.parsers.xmlproc import xmlval

class docErrorHandler(xmlval.ErrorHandler):
  def warning(self, message):
    print(message)
  def error(self, message):
    print(message)
  def fatal(self, message):
    print(message)

parser=xmlval.XMLValidator()
parser.set_error_handler(docErrorHandler(parser))
parser.parse_resource("configfile.xml")

Exercise 3 Solution

#!/usr/bin/python

from xml.sax         import make_parser
from xml.sax.handler import ContentHandler

#begin configHandler
class configHandler(ContentHandler):
  inUtildir = False
  utildir = ''
  inUtil = False
  util = ''
  inMode = False
  mode = ''

  def startElement(self, name, attributes):

    if name == "utilitydirectory":
      self.inUtildir = True

    elif name == "utility":
      self.inUtil = True

    elif name == "mode":
      self.inMode = True

  def endElement(self, name):
    if name == "utilitydirectory":
      self.inTitle = False

    elif name == "utility":
      self.inUtil = False

    elif name == "mode":
      self.inMode = False
def characters(self, content):
    if self.inUtildir:
      utildir = utildir + content
    elif self.inUtil:
      util = util + content
    elif self.inMode:
      mode = mode + content
#end configHandler

parser  = make_parser()
parser.setContentHandler(configHandler())
parser.parse("configfile.xml")

#....Do stuff with config information here

Chapter 16

  1. Distinguish between the following e-mail-related standards: RFC 2822, SMTP, IMAP, MIME, and POP.

  2. Write a script that connects to a POP server, downloads all of the messages, and sorts the messages into files named after the sender of the message. (For instance, if you get two e-mails from , they should both go into a file "").

    What would be the corresponding behavior if you had an IMAP server instead? Write that script, too (use RFC 3501 as a reference).

  3. Suppose that you were designing an IRC-style protocol for low-bandwidth embedded devices such as cell phones. What changes to the Python Chat Server protocol would it be useful to make?

  4. A feature of IRC not cloned in the Python Chat Server is the /msg command, which enables one user to send a private message to another instead of broadcasting it to the whole room. How could the /msg command be implemented in the Python Chat Server?

  5. When does it make sense to design a protocol using a peer-to-peer architecture?

Exercise 1 Solution

RFC 2822 is a file format standard that describes what e-mail messages should look like.

MIME is a file format standard that describes how to create e-mail messages that contain binary data and multiple parts, while still conforming to RFC 2822.

SMTP is a protocol used to deliver an e-mail message to someone else.

POP is a protocol used to pick up your e-mail from your mail server.

IMAP is a newer protocol that does the same job as POP. It's intended to keep the e-mail on the server permanently, instead of just keeping it until you pick it up.

Exercise 2 Solution

Here's a script that uses POP:

#!/usr/bin/python
from poplib import POP3
from email import parser

#Connect to the server and parse the response to see how many messages there
#are, as in this chapter's previous POP example.
server = POP3("pop.example.com")
server.user("[user]")
response = server.pass_("[password]")
numMessages = response[response.rfind(', ')+2:]
numMessages = int(numMessages[:numMessages.find(' ')])

#Parse each email and put it in a file named after the From: header of
#the mail.
parser = parser()
openFiles = {}
for messageNum in range(1, numMessages+1):
    messageString = '
'.join(server.retr(messageNum)[1])
    message = email.parsestr(messageString, True)
    fromHeader = message['From']
    mailFile = openFiles.get(fromHeader)
    if not mailFile:
        mailFile = open(fromHeader, 'w')
        openFiles[fromHeader] = mailFile
    mailFile.write(messageString)
    mailFile.write('
')
#Close all the files to which we wrote mail.
for openFile in openFiles.values():
    openFile.close()

Because IMAP enables you to sort messages into folders on the server, an IMAP version of this script can simply create new mailboxes and move messages into them. Here's a script that does just that:

#!/usr/bin/python
from imaplib import IMAP4
import email
import re

#Used to parse the IMAP responses.
FROM_HEADER = 'From: '
IMAP_UID = re.compile('UID ([0-9]+)')

#Connect to the server.
server = IMAP4('imap.example.com')
server.login('[username]', '[password]')
server.select('Inbox')

#Get the unique IDs for every message.
uids = server.uid('SEARCH', 'ALL')[1][0].split(' ')
uidString = ','.join(uids)
#Get the From: header for each message
headers = server.uid('FETCH', '%s' % uidString,
                     '(BODY[HEADER.FIELDS (FROM)])')
for header in headers[1]:
    if len(header) > 1:
        uid, header = header
        #Parse the IMAP response into a real UID and the value of the
        #'From' header.
        match = IMAP_UID.search(uid)
        uid = match.groups(1)[0]

        fromHeader = header[len(FROM_HEADER):].strip()

        #Create the mailbox corresponding to the person who sent this
        #message. If it already exists the server will throw an error,
        #but we'll just ignore it.
        server.create(fromHeader)

        #Copy this message into the mailbox.
        server.uid('COPY', uid, fromHeader)

#Delete the messages from the inbox now that they've been filed.
server.uid('STORE', uidString, '+FLAGS.SILENT', '(\Deleted)')
server.expunge()

Exercise 3 Solution

In general, move as much text as possible out of the protocol and into the client software, which needs to be downloaded only once. Some specific suggestions:

  • Send short status codes instead of English sentences: for instance, send "HELLO" instead of "Hello [nickname], welcome to the Python Chat Server!".

  • Assign a number to every user in the chat room, and send the number instead of their nickname whenever they do something — for instance, broadcast '4 Hello' instead of '<user> Hello' whenever a user sends a message.

  • Use a compression technique to make the chat text itself take up less bandwidth.

Exercise 4 Solution

The easiest way is to simply define a method 'msgCommand' and let the _parseCommand dispatch it. Here's a simple implementation of msgCommand:

def msgCommand(self, nicknameAndMsg):
    "Send a private message to another user."
    if not ' ' in nicknameAndMsg:
       raise ClientError('No message specified.')
    nickname, msg = nicknameAndMsg.split(' ', 1)
    if nickname == self.nickname:
        raise ClientError('What, send a private message to yourself?')
    user = self.server.users.get(nickname)
    if not user:
raise ClientError('No such user: %s' % nickname)
msg = '[Private from %s] %s' % (self.nickname, msg)
xsuser.write(self._ensureNewline(msg))

Exercise 5 Solution

  • The peer-to-peer architecture is more general than the client-server architecture. The peer-to-peer design of TCP/IP makes it a flexible general-purpose protocol. It's easier to implement a client-server protocol atop TCP/IP than it is to implement a peer-to-peer design on top of a client-server protocol. If you want a general-purpose protocol, try to preserve the peer-to-peer nature of TCP/IP.

  • Consider using peer-to-peer when it makes sense for a client to download some data from a server and then immediately start serving it to other clients. A peer-to-peer architecture for the distribution of e-mail doesn't make sense, because most e-mail is addressed to one person only. Once that person has downloaded the e-mail, it shouldn't be automatically distributed further. A peer-to-peer architecture for the distribution of newsletters makes more sense.

  • Peer-to-peer is most useful when you have some way of searching the network. When a network resource doesn't have a single, unambiguous location (the way a file hosted on a web server does), it's more difficult to find what you want, and search facilities are more important.

Chapter 17

  1. Add a new module-level function to the foo module you created earlier in the chapter. Call the function reverse_tuple and implement it so that it accepts one tuple as an argument and returns a similarly sized tuple with the elements in reverse order. Completing this exercise is going to require research on your part because you need to know how to "unpack" a tuple. You already know one way to create a tuple (using Py_BuildValue), but that's not going to work for this exercise, because you want your function to work with tuples of arbitrary size. The Python/C API documentation for tuples (at http://docs.python.org/api/tupleObjects.html) lists all of the functions you need to accomplish this. Be careful with your reference counting!

  2. List and dictionary objects are an extremely important part of nearly all Python applications so it would be useful to learn how to manipulate those objects from C. Add another function to the foo module called dict2list that accepts a dictionary as a parameter and returns a list. The members of the list should alternate between the keys and the values in the dictionary. The order isn't important as long as each key is followed by its value. You'll have to look up how to iterate over the items in the dictionary (hint: look up PyDict_Next) and how to create a list and append items to it (hint: look up PyList_New and PyList_Append).

Chapter 18

  1. Write a function that expresses a number of bytes as the sum of gigabytes, megabytes, kilobytes, and bytes. Remember that a kilobyte is 1024 bytes, a megabyte is 1024 kilobytes, and so on. The number of each should not exceed 1023. The output should look something like this:

    >>> print(format_bytes(9876543210))
    9 GB + 203 MB + 5 KB + 746 bytes
  2. Write a function that formats an RGB color in the color syntax of HTML. The function should take three numerical arguments: the red, green, and blue color components, each between zero and one. The output is a string of the form #RRGGBB, where RR is the red component as a value between 0 and 255, expressed as a two-digit hexadecimal number, and GG and BB likewise for the green and blue components.

    For example:

    >>> print(rgb_to_html(0.0, 0.0, 0.0)  # black)
    #000000
    >>> print(rgb_to_html(1.0, 1.0, 1.0)  # white)
    #ffffff
    >>> print(rgb_to_html(0.8, 0.5, 0.9)  # purple)
    #cc80e6
  3. Write a function named normalize that takes an array of float numbers and returns a copy of the array in which the elements have been scaled such that the square root of the sum of their squares is one. This is an important operation in linear algebra and other fields.

    Here's a test case:

    >>> for n in normalize((2.2, 5.6, 4.3, 3.0, 0.5)):
    ...   print("%.5f" % n,)
    ...
    0.27513 0.70033 0.53775 0.37518 0.06253

Exercise 1 Solution

def format_bytes(bytes):
    units = (
        ("GB", 1024 ** 3),
        ("MB", 1024 ** 2),
        ("KB", 1024 ** 1),
        ("bytes", 1),
        )
    terms = []
    for name, scale in units:
        if scale > bytes:
            continue
        # Show how many of this unit.
        count = bytes // scale
        terms.append("%d %s" % (count, name))
        # Compute the leftover bytes.
        bytes = bytes % scale
    # Construct the full output from the terms.
    return " + ".join(terms)

Exercise 2 Solution

def rgb_to_html(red, green, blue):
    # Convert floats between zero and one to ints between 0 and 255.
    red = int(round(red * 255))
    green = int(round(green * 255))
blue = int(round(blue * 255))
    # Write out HTML color syntax.
    return "#%02x%02x%02x" % (red, green, blue)

Exercise 3 Solution

Solution using a list of numbers:

from math import sqrt

def normalize(numbers):
    # Compute the sum of squares of the numbers.
    sum_of_squares = 0
    for number in numbers:
        sum_of_squares += number * number
    # Copy the list of numbers.
    result = list(numbers)
    # Scale each element in the list.
    scale = 1 / sqrt(sum_of_squares)
    for i in xrange(len(result)):
        result[i] *= scale
    return result

This very concise numarray version works only when called with a numarray.array object. You can convert a different array type with numbers = numarray.array(numbers):

from math import sqrt
import numarray

def normalize(numbers):
    return numbers / sqrt(numarray.sum(numbers * numbers))

Chapter 19

  1. Configure the __settings.py file to work with each type of database that Django supports.

  2. Explain the MVC and MTV architectures and elaborate on the difference between the two.

  3. Create a template that shows the menu from a restaurant and have it display.

  4. Working with the same data fields you used in exercise 3, create a model that shows a menu from a restaurant and have Django create the database.

Chapter 20

  1. What's a RESTful way to change BittyWiki so that it supports hosting more than one Wiki?

  2. Write a web application interface to WishListBargainFinder.py. (That is, a web application that delegates to the Amazon Web Services.)

  3. The wiki search-and-replace spider looks up every new WikiWord it encounters to see whether it corresponds to a page of the wiki. If it finds a page by that name, that page is processed. Otherwise, nothing happens and the spider has wasted a web service request. How could the web service API be changed so that the spider could avoid those extra web service requests for nonexistent pages?

  4. Suppose that, to prevent vandalism, you change BittyWiki so that pages can't be deleted. Unfortunately, this breaks the wiki search-and-replace spider, which sometimes deletes a page before re-creating it with a new name. What's a solution that meets both your needs and the needs of the spider's users?

Exercise 1 Solution

Put the name of the wiki in the resource identifier, before the page name: Instead of "/PageName", it would be "/Wikiname/PageName". This is RESTful because it puts data in the resource identifier, keeping it transparent. Not surprising, this identifier scheme also corresponds to the way the wiki files would be stored on disk.

Exercise 2 Solution

#!/usr/bin/python
import cgi
import cgitb
import os
from WishListBargainFinder import BargainFinder, getWishList
cgitb.enable()

SUBSCRIPTION_ID = '[Insert your subscription ID here.]'
SUBSCRIPTION_ID = 'D8O1OTR10IMN7'

form = cgi.FieldStorage()
wishListID = form.getfirst('wishlist', '')

args = {'title' : 'Amazon Wish List Bargain Finder',
        'action' : os.environ['SCRIPT_NAME'],
        'wishListID' : wishListID}

print('Content-type: text/html
')
print('''<html><head><title>%(title)s</title></head>)
<form method="get" action="%(action)s">
<h1>%(title)s</h1>
Enter an Amazon wish list ID:
<input name="wishlist" length="13" maxlength="13" value="%(wishListID)s" />
<input type="submit" value="Find bargains"/>
</form>''' % args

if wishListID:
    print('<pre>')
    BargainFinder().printBargains(getWishList(SUBSCRIPTION_ID, wishListID))
    Print('</pre>')

print('</body></html>')

Note that this points to an improvement in BargainFinder: creating a method that returns the bargain information in a data structure, which can be formatted in plaintext, HTML, or any other way, instead of just printing the plaintext of the bargains.

Exercise 3 Solution

For REST: The BittyWiki web application already outputs rendered HTML because that's what web browsers know how to parse. However, a BittyWiki page served by the web application includes navigation links and other elements besides just a rendering of the page text. If web service users aren't happy scraping away that extraneous HTML to get to the actual page text, or if you want to save bandwidth by not sending that HTML in the first place, there are two other solutions. The first is to have web service clients provide the HTTP Accept header in GET requests to convey whether they want the "text/plain" or "text/html" flavor of the resource. The second is to provide different flavors of the same document through different resources. For instance, /bittywiki-rest.py/PageName.txt could provide the plaintext version of a page, and /bittywiki-rest.py/PageName.html could provide the rendered HTML version of the same page.

For XML-RPC and SOAP, the decision is simpler. Just have clients pass in an argument to getPage specifying which flavor of a page they want.

Exercise 4 Solution

This could be fixed by changing the GET resource or getPage API call to return not only the raw text of the page, but a representation of which WikiWords on the page correspond to existing pages. This could be a list of WikiWords that have associated pages, or a dictionary that maps all of the page's referenced WikiWords to True (if the word has an associated page) or False (if not). The advantage of the second solution is that it could save the robot side from having to keep its own definition of what constitutes a WikiWord.

Exercise 5 Solution

Create a new API call specifically for renaming a page. In XML-RPC or SOAP, this would be as simple as creating a rename function and removing the delete function. For a REST API, you might add a capability to the POST request that creates a new wiki page: Instead of providing the data, let it name another page of the wiki to use as the data source, with the understanding that the other page will be deleted afterward.

Chapter 21

  1. If Python is so cool, why in the world would anyone ever use another programming language such as Java, C++, C#, Basic, or Perl?

  2. The Jython interpreter is written in what programming language? The python command is written in what programming language?

  3. When you package a Jython-based application for running on another system, what do you need to include?

  4. Can you use the Python DB driver modules, such as those described in Chapter 14, in your Jython scripts?

  5. Write a Jython script that creates a window with a red background using the Swing API.

Exercise 1 Solution

Many organizations have an investment in another programming language. Jython, though, enables you to use Python in a Java environment.

Exercise 2 Solution

Jython is written in Java. The python interpreter is written in C.

Exercise 3 Solution

You need to include your Jython scripts, of course, but also the following:

  • The jython.jar Java library

  • The Jython Lib directory

  • The Jython cachedir directory. This directory must be writeable.

Exercise 4 Solution

No, unless the DB drivers are written in Python or Java. Most Python DB drivers are written in C and Python, and so cannot run from Jython (without a lot of work with the Java Native Interface, or JNI). Luckily, the Jython zxJDBC module enables you to call on any JDBC driver from your Jython scripts. This opens up your options to allow you to access more databases than those for which you can get Python DB drivers.

Exercise 5 Solution

This is probably the simplest way to create such a window:

from javax.swing import JFrame

frame = JFrame(size=(500,100))

# Use a tuple for RGB color values.
frame.background = 255,0,0

frame.setVisible(1)

You can get fancy and add widgets such as buttons and labels, if desired.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.84.169