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?
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?
'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.
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.
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.
In the Python shell, multiply 5 and 10. Try this with other numbers as well.
Print every number from 6 through 14 in base 8.
Print every number from 9 through 19 in base 16.
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
.
>>> 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
>>> 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
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.
Perform all of the following in the Python shell:
Create a list called dairy_section
with four elements from the dairy section of a supermarket.
Print a string with the first and last elements of the dairy_section
list.
Create a tuple called milk_expiration
with three elements: the month, day, and year of the expiration date on the nearest carton of milk.
Print the values in the milk_expiration
tuple in a string that reads "This milk carton will expire on 12/10/2005."
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.
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.
Show how to calculate the cost of six cartons of milk based on the cost of milk_carton
.
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.
How do you count the number of cheeses in the cheese list?
Print out the first five letters of the name of your first cheese.
>>> print("First: %s and Last %s" % (dairy_section[0], dairy_section[1])) First: milk and Last cottage cheese
>>> 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
>>> milk_carton = {} >>> milk_carton["expiration_date"] = milk_expiration >>> milk_carton["fl_oz"] = 32 >>> milk_carton["cost"] = 1.50 >>> milk_carton["brand_name"] = "Milk"
>>> 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
>>> print("The cost for 6 cartons of milk is %.02f" % (6* milk_carton["cost"])) The cost for 6 cartons of milk is 9.00
>>> cheeses = ["cheddar", "american", "mozzarella"] >>> dairy_section.append(cheeses) >>> dairy_section ['milk', 'cottage cheese', 'butter', 'yogurt', ['cheddar', 'american', 'mozzarella']] >>> dairy_section.pop() ['cheddar', 'american', 'mozzarella']
Perform all of the following in the codeEditor Python shell:
Using a series of if ... :
statements, evaluate whether the numbers from 0 through 4 are True or False by creating five separate tests.
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.
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.
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.
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.
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.
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
>>> 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
>>> 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
>>> 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
>>> 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
>>> 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
Write a function called do_plus
that accepts two parameters and adds them together with the "+
" operation.
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.
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:
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.
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.
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.
Rather than a cheese omelet, choose a different default omelet to make. Add the ingredients for this omelet to the get_omelet_ingredients
function.
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.
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
# 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)
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.
Each of the following exercises builds on the exercises that preceded it:
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.
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.
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).
View the docstrings that you've created by creating an Omelet
object.
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.
Alter the __init__
method of Omelet
so that it accepts a Recipe
class. To do this, you can do the following:
Create a name, self.recipe
, that each Omelet
object will have.
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.
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.
In addition, modify __known_kinds
to use the recipe method's get
method to find out the ingredients of an omelet.
Try using all of the new classes and methods to determine whether you understand them.
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
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()
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 """
>>> 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.
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
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)
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.
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.
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.
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.
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.")
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")
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
.)
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.
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)
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 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.
How can you get access to the functionality provided by a module?
How can you control which items from your modules are considered public? (Public items are available to other Python scripts.)
How can you view documentation on a module?
How can you find out what modules are installed on a system?
What kind of Python commands can you place in a module?
You get access to the functionality with a module by importing the module or items from the module.
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.
The help
function displays help on any module you have imported. The basic syntax follows:
help(module)
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.
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.
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, /).
Modify the scan_pdy.py
script to match only PDF files with the text boobah in the file name.
Modify the scan_pdf.py
script to exclude all files with the text boobah in the file name.
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.
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.
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.
Experiment with different layouts using different pack orders.
Practice modifying the look of your widgets by changing every property.
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?
Set up a relational database such as MySQL or Sqlite.
Use a dbm module such as dbm.
Implement a web-service-backed rich web application to create a buzzword-compliant application.
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
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.
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.
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
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()
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>
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)*>
Use SAX to extract configuration information from the preceding config
file instead of DOM.
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.....
#!/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")
#!/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
Distinguish between the following e-mail-related standards: RFC 2822, SMTP, IMAP, MIME, and POP.
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 [email protected], they should both go into a file "[email protected]").
What would be the corresponding behavior if you had an IMAP server instead? Write that script, too (use RFC 3501 as a reference).
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?
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?
When does it make sense to design a protocol using a peer-to-peer architecture?
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.
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()
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.
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))
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.
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!
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).
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
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
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
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)
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)
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))
Configure the __settings.py file to work with each type of database that Django supports.
Explain the MVC and MTV architectures and elaborate on the difference between the two.
Create a template that shows the menu from a restaurant and have it display.
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.
What's a RESTful way to change BittyWiki so that it supports hosting more than one Wiki?
Write a web application interface to WishListBargainFinder.py
. (That is, a web application that delegates to the Amazon Web Services.)
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?
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?
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.
#!/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.
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.
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.
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.
If Python is so cool, why in the world would anyone ever use another programming language such as Java, C++, C#, Basic, or Perl?
The Jython interpreter is written in what programming language? The python
command is written in what programming language?
When you package a Jython-based application for running on another system, what do you need to include?
Can you use the Python DB driver modules, such as those described in Chapter 14, in your Jython scripts?
Write a Jython script that creates a window with a red background using the Swing API.
Many organizations have an investment in another programming language. Jython, though, enables you to use Python in a Java environment.
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.
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.
3.147.84.169