Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Python (2005)

.pdf
Скачиваний:
158
Добавлен:
17.08.2013
Размер:
15.78 Mб
Скачать

Functions

If you write a function with more than one parameter and you want to have both required and optional parameters, you have to place the optionals at the end of your list of parameters. This is because once you’ve specified that a parameter is optional, it may or may not be there. From the first optional parameter, Python can’t guarantee the presence of the remaining parameters — those to the right of your optional parameters. In other words, every parameter after the first default parameter becomes optional. This happens automatically, so be careful and be aware of this when you use this feature.

Calling Functions from within Other Functions

Functions declared within the top level, or global scope, can be used from within other functions and from within the functions inside of other functions. The names in the global scope can be used from everywhere, as the most useful functions need to be available for use within other functions.

In order to have a to be available, so

make_omelet function work the way you saw above, it should rely on other functions they can be used by make_omelet.

This is how it should work: First, a function acts like sort of a cookbook. It will be given a string that names a type of omelet and return a dictionary that contains all of the ingredients and their quantities. This function will be called get_omelet_ingredients, and it needs one parameter — the name of the 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

else:

print “That’s not on the menu, sorry!” return None

return ingredients

The second function you need to make omelets is a function called make_food that takes two parameters. The first is a list of ingredients needed — exactly what came from the get_omelet_ingredients function. The second is the name of the food, which should be the type of omelet:

def make_food(ingredients_needed, food_name): “””make_food(ingredients_needed, food_name)

Takes the ingredients from ingredients_needed and makes food_name””” for ingredient in ingredients_needed.keys():

print “Adding %d of %s to make a %s” % (ingredients_needed[ingredient], ingredient, food_name)

print “Made %s” % food_name return food_name

71

TEAM LinG

Chapter 5

At this point, all of the pieces are in place to use the make_omelet function. It needs to call on the get_omelet_ingredients and the make_food functions to do its job. Each function provides some part of the process of making an omelet. The get_omelet_ingredients provides the specific instructions for specific kinds of omelets, while the make_food function provides the information needed to know that any kind of food can, if you look at it one way (a very simplistic way for the sake of demonstration!), be represented as the result of just mixing the right quantities of a number of ingredients.

Try It Out

Invoking the Completed Function

Now that you have all of the functions in place for make_omelet to work, invoke your ch5.py file with python -i or the Run with Interpreter command, and then try out the following code in the shell:

>>> omelet_type = make_omelet(“cheese”) Adding 2 of eggs to make a cheese Adding 2 of cheddar to make a cheese Adding 1 of milk to make a cheese

Made cheese

>>>print omelet_type cheese

>>>omelet_type = make_omelet({“eggs”:2, “jack_cheese”:2, “milk”:1, “mushrooms”:2}) omelet_type is a dictionary with ingredients

Adding 2 of jack_cheese to make a omelet Adding 2 of mushrooms to make a omelet Adding 2 of eggs to make a omelet Adding 1 of milk to make a omelet

Made omelet

>>>print omelet_type

omelet

How It Works

Now that all of the functions are in place and can be called, one from another, make_omelet can be used by only specifying the name of the omelet that you want to make.

Functions Inside of Functions

While it’s unlikely that you’ll be modeling any omelet-making in your professional or amateur career, the same process of designing partial simulations of real-world situations is likely, so this section will provide some ideas about how you could refine the solution you already have.

You may decide that a particular function’s work is too much to define in one place and want to break it down into smaller, distinct pieces. To do this, you can place functions inside of other functions and have them invoked from within that function. This allows for more sense to be made of the complex function. For instance, get_omelet_ingredients could be contained entirely inside the make_omelet function and not be available to the rest of the program.

Limiting the visibility of this function would make sense, as the usefulness of the function is limited to making omelets. If you were writing a program that had instructions for making other kinds food as well, the ingredients for omelets wouldn’t be of any use for making these other types of food, even similar foods like scrambled eggs or soufflés. Each new food would need its own functions to do the same

72

TEAM LinG

Functions

thing, with one function for each type of food. However, the make_food function would still make sense on its own and could be used for any kind of food.

Defining a function within another function looks exactly like defining it at the top level. The only difference is that it is indented at the same level as the other code in the function in which it’s contained. In this case, all of the code looks exactly the same:

def make_omelet(omelet_type):

“””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”””

def get_omelet_ingredients(omelet_name):

“””This contains a dictionary of omelet names that can be produced, and their ingredients”””

ingredients = {“eggs”:2, “milk”:1} if omelet_name == “cheese”:

ingredients[“cheddar”] = 2 elif omelet_name == “western”:

ingredients[“jack_cheese”] = 2

# You need to copy in the remainder of the original

# get_omelet_ingredients function here. They are not being

#included here for brevity’s sake if type(omelet_type) == type({}):

print “omelet_type is a dictionary with ingredients” return make_food(omelet_type, “omelet”)

elif type(omelet_type) == type(“”):

omelet_ingredients = get_omelet_ingredients(omelet_type) return make_food(omelet_ingredients, omelet_type)

else:

print “I don’t think I can make this kind of omelet: %s” % omelet_type

It is important to define a function before it is used. If an attempt is made to invoke a function before it’s defined, Python won’t be aware of its existence at the point in the program where you’re trying to invoke it, and so it can’t be used! Of course, this will result in an error and an exception being raised. So, define your functions at the beginning of your files so you can use them toward the end.

Flagging an Error on Your Own Terms

If you need to indicate that a particular error has occurred, you may want to use one of the errors you’ve already encountered to indicate, through the function that’s being called, what has gone wrong.

There is a counterpart to the try: and except: special words: the raise ... command. A good time to use the raise ... command might be when you’ve written a function that expects multiple parameters but one is of the wrong type.

You can check the parameters that are passed in and use raise ... to indicate that the wrong type was given. When you use raise ..., you provide a message that an except ... : clause can capture for display — an explanation of the error.

73

TEAM LinG

Chapter 5

The following code changes the end of the make_omelet function by replacing a printed error, which is suitable for being read by a person running the program, with a raise ... statement that makes it possible for a problem to be either handled by functions or printed so that a user can read it:

if type(omelet_type) == type({}):

print “omelet_type is a dictionary with ingredients” return make_food(omelet_type, “omelet”)

elif type(omelet_type) == type(“”):

omelet_ingredients = get_omelet_ingredients(omelet_type) return make_food(omelet_ingredients, omelet_type)

else:

raise TypeError, “No such omelet type: %s” % omelet_type

After making this change, make_omelet can give you precise information about this kind of error when it’s encountered, and it still provides information for a user.

Layers of Functions

Now that you’ve got an idea of what functions are and how they work, it’s useful to think about them in terms of how they are called and how Python keeps track of these layers of invocations.

When your program calls a function, or a function calls a function, Python creates a list inside of itself that is called the stack or sometimes the call stack. When you invoke a function (or call on, which is why it can be called a call stack), Python will stop for a moment, take note of where it is when the function was called and then stash that information in its internal list. It’ll then enter the function and execute it, as you’ve seen. For example, the following code illustrates how Python keeps track of how it enters and leaves functions:

[{‘top_level’: ‘line 1’}, {‘make_omelet’: ‘line 64’}, {‘make food’: ‘line 120’}]

At the top, Python keeps track starting at line 1. Then, as the function make_omelet is called at line sixty-four, it keeps track of that. Then, from inside of make_omelet, make_food is called. When the make_food function finishes, Python determines that it was on line 64, and it returns to line 64 to continue. The line numbers in the example are made up, but you get the idea.

The list is called a stack because of the way in which a function is entered. You can think of a function as being on the top of a stack until it is exited, when it is taken off, and the stack is shortened by one.

How to Read Deeper Errors

When an error does happen in a program and an uncaught error is raised, you might find yourself looking at a more complex error than what you’ve seen before. For example, imagine that you’ve passed a dictionary that contains a list instead of a number. This will cause an error that looks like the following:

>>> make_omelet({“a”:1, “b”:2, “j”:[“c”, “d”, “e”]}) omelet_type is a dictionary with ingredients

Adding 1 of a to make a omelet

Adding 2 of b to make a omelet

74

TEAM LinG

Functions

Traceback (most recent call last): File “<stdin>”, line 1, in ?

File “ch5.py”, line 96, in make_omelet return make_food(omelet_type, “omelet”)

File “ch5.py”, line 45, in make_food

print “Adding %d of %s to make a %s” % (ingredients_needed[ingredient], ingredient, food_name)

TypeError: int argument required

After you’ve entered a function from a file, Python will do its best to show you where in the stack you are (which means how many layers there are when the error occurs and at what line in the file each layer in the stack was called from) so that you can open the problem file to determine what happened.

As you create deeper stacks (which you can think of as longer lists) by calling more functions or using functions that call other functions, you gain experience in using the stack trace. (This is the common name for the output that Python gives you when you raise an error or when an exception is otherwise raised.)

With the preceding stack trace, which is three levels deep, you can see that in line 45, when make_food is called, there was a problem with the type of an argument. You could now go back and fix this.

If you thought that this problem would happen a lot, you could compensate for it by enclosing calls to make_food in a try ...: block so that TypeErrors can always be prevented from stopping the program. However, it’s even better if you handle them in the function where they will occur.

In the case of something like a blatantly incorrect type or member of a dictionary, it’s usually not necessary to do any more than what Python does on its own, which is to raise a TypeError. How you want to handle any specific situation is up to you, however.

The stack trace is the readable form of the stack, which you can examine to see where the problem happened. It shows everything that is known at the point in time when a problem occurred, and it is produced by Python whenever an exception has been raised.

Summar y

This chapter introduced you to functions. Functions are a way of grouping a number of statements in Python into a single name that can be invoked any time that it’s needed. When a function is defined, it can be created so that when it’s invoked it will be given parameters to specify the values on which it should operate.

The names of the parameters for a function are defined along with the function by enclosing them in parentheses after the function is named. When no parameters are used, the parentheses are still present, but they will be empty.

As functions are invoked, they each create a scope of their own whereby they have access to all of the names that are present in the global scope of the program, as well as names that have been assigned and created inside of the function. If a name that is present in the global scope is assigned in the scope of a

75

TEAM LinG

Chapter 5

particular function, it will not change value when referenced by the global name but will instead only be changed within the function.

If a function is defined within another function, then it can access all of the names of the function in which it was defined, as well as names that are in the global scope. Remember that this visibility depends on where the function is defined and not where it was called.

Functions can be called from within other functions. Doing this can make understanding programs easier. Functions enable you to reduce repetitive typing by making common tasks achievable with a brief name.

Functions that are defined with parameters are invoked with values — each value provided will be assigned, in the function, to the name inside the function’s parameter list. The first parameter passed to a function will be assigned to the first name, the second to the second, and so on. When functions are passed parameters, each one can be either mandatory or optional. Optional parameters must be placed after mandatory parameters when the function is defined, and they can be given a default value.

You can use the raise . . . : feature to signal errors that can be received and handled by except . . . :. This enables you to provide feedback from your functions by providing both the type of error and a string that describes the error so it can be handled.

You have also learned about the stack. When an error condition is raised with raise . . . :, or by another error in the program, the location of the error is described not just by naming the function where the error occurred, but also by naming any and all intervening functions that were invoked and specifying on what line in which file that invocation happened. Therefore, if the same function is useful enough that you use it in different places and it only has problems in one of them, you can narrow the source of the problem by following the stack trace that is produced.

Exercises

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:

a.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.

b.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.

76

TEAM LinG

Functions

c.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.

d.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.

77

TEAM LinG

TEAM LinG

6

Classes and Objects

So far, you have been introduced to most of the building blocks of programming. You have used data; you have referenced that data to names (the names are more commonly called variables when programmers talk); and you have used that data in loops and functions. The use of these three elements are the foundation of programming and problem-solving with computers. Named variables enable you to store values, reference them, and manipulate them. Repeating loops enable you to evaluate every possible element in a list, or every other element, or ever third element, and so on. Finally, functions enable you to combine bunches of code into a name that you can invoke whenever and wherever you need it.

In this chapter, you will see how Python provides a way to combine functions and data so that they are accessed using a single object’s name. You’ll also gain some knowledge about how and why classes and objects are used and how they make programs easier to write and use in a variety of circumstances.

Thinking About Programming

At this point, you’ve only been given a rudimentary introduction to Python. To create a description of an object in Python right now, you have just enough knowledge to achieve two views. One is of the data, which comes and goes as needed, except for parts that live in the top level, or global scope. The other view is of functions, which have no persistent data of their own. They interact only with data that you give them.

Objects You Already Know

The next tool you will be given will enable you to think of entire objects that contain both data and functions. You’ve already seen these when you used strings. A string is not just the text that it contains. As you’ve learned, methods are associated with strings, which enable them to be more than just the text, offering such features as allowing you to make the entire string upper or lowercase. To recap what you’ve already learned, a string is mainly the text that you’ve input:

>>> omelet_type = “Cheese”

TEAM LinG

Chapter 6

In addition to the data that you’ve worked with the most, the text “Cheese,” the string is an object that has methods, or behaviors that are well known. Examples of methods that every string has are lower, which will return the string it contains as all lowercase, and upper, which will return the string as an entirely uppercase string:

>>>omelet_type.lower() ‘cheese’

>>>omelet_type.upper() ‘CHEESE’

Also available are methods built into tuple, list, and dictionary objects, like the keys method of dictionaries, which you’ve already used:

>>>fridge = {“cheese”:1, “tomato”:2, “milk”:4}

>>>fridge.keys()

[‘tomato’, ‘cheese’, ‘milk’]

When you want to find out more about what is available in an object, Python exposes everything that exists in an object when you use the dir function:

dir(omelet_type)

[‘__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__doc__’, ‘__eq__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’, ‘__getnewargs__’, ‘__getslice__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__len__’, ‘__lt__’, ‘__mod__’, ‘__mul__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__rmod__’, ‘__rmul__’, ‘__setattr__’, ‘__str__’, ‘capitalize’, ‘center’, ‘count’, ‘decode’, ‘encode’, ‘endswith’, ‘expandtabs’, ‘find’, ‘index’, ‘isalnum’, ‘isalpha’, ‘isdigit’, ‘islower’, ‘isspace’, ‘istitle’, ‘isupper’, ‘join’, ‘ljust’, ‘lower’, ‘lstrip’, ‘replace’, ‘rfind’, ‘rindex’, ‘rjust’, ‘rsplit’, ‘rstrip’, ‘split’, ‘splitlines’, ‘startswith’, ‘strip’, ‘swapcase’, ‘title’, ‘translate’, ‘upper’, ‘zfill’]

Every bit of data, every method, and, in short, every name in a string or any other object in Python can be exposed with the dir function. dir lists all of the available names in the object it is examining in alphabetical order, which tends to group those names beginning with underscores first. By convention, these names refer to items considered to be internal pieces of the object and should be treated as though they are invisible. In other words, you shouldn’t use them, but Python leaves that decision up to you — there’s no reason not to look at these items interactively to learn from them:

>>> type(omelet_type.__len__) <type ‘method-wrapper’>

This is interesting. Because this is a method, it can be invoked to see what it does:

>>> omelet_type.__len__() 6

This returns the same value as the len built-in function. When a function is built in to an object, it’s called a method of that object.

80

TEAM LinG