Explore 2

Beautiful Code
Ka-Ping Yee, March 5, 2003

Note 1: please send all e-mail regarding this class to bc@zesty.ca. Don't use my personal address. This will help me keep all the mail sorted. I get way too much junk mail, so if you send mail to my personal address it may get overlooked.

Note 2: you should view this page in a browser that supports CSS, if you can. In the lab, i believe the only such browser is Mozilla (run mozilla).

Welcome to Explore 2. I know that doesn't make any grammatical sense, but what else can i call it? I considered "exploration" but that is long and stupid-sounding (like "meditation 2" – ha! sounds like it oughta be the title of a work of classical music or some incomprehensible modern art piece).

Sorry. Okay, back to Python (the persons responsible for the paragraph above this one have just been sacked).

1. Functions

Functions are the big topic of the day. They should be quite familiar to you from other programming languages. Python's function definitions are pretty straightforward; it should be easy to see what this one does, for example.

>>> def answer_phone():
...     print 'Hello, may I take your order?

The nice thing about functions is that they let you teach the computer how to do something, and then you can ask the computer to do that thing as many times as you like.

>>> answer_phone()
Hello, may I take your order?
>>> answer_phone()
Hello, may I take your order?

Functions can take parameters, to specialize what they do. For instance:

>>> def answer_phone(big_cheese):
...     print 'Hello, ' + big_cheese + "'s pizza."
...     print 'May I take your order?'
>>> answer_phone('Tony')
Hello, Tony's pizza.
May I take your order?
>>> answer_phone('Domino')
Hello, Domino's pizza.
May I take your order?
>>> answer_phone('Guido')
Hello, Guido's pizza.
May I take your order?

The function body must be indented all to a consistent depth (usually four spaces). If the indentation changes randomly in the middle of a block, you'll get a syntax error (the message will talk about an "indentation error", actually).

Notice that you need to write the parentheses in the definition even if the function has no parameters. You must also write the parentheses when you call a function. Whenever you use parentheses for this purpose, never leave a space between the function name and the left parenthesis. (This stylistic rule visually distinguishes function calls from parentheses used for grouping in expressions, and thereby makes your code easier to read.)

Q1. What happens if you just type in answer_phone without the parentheses? What do you think that means?

Q2. Write a function called compare that accepts two parameters and prints a message explaining which parameter is bigger or if they are the same. For example, compare(3, 5) should print a message like 3 is smaller than 5.

2. Docstrings

Try help(answer_phone).

Documentation strings, or "docstrings" for short, explain what your functions do. Just about every function you write should have a docstring, except perhaps for functions that are so simple that their purpose is obvious from the function's name.

To write one, just put a string right at the beginning of the function body. By convention, docstrings are always written with three double-quote marks at the beginning and the end. The triple-quotes let them easily extend over several lines. Another convention is that docstrings usually start with a succinct, one-line summary; after that they can explain stuff in more detail if necessary.

>>> def mock():
...     """Mock the user mercilessly.
...     Print out a well-known insult from "Monty Python
...     and the Holy Grail".
...     """
...     print 'Your mother was a hamster,'
...     print 'and your father smelt of elderberries!'

Many functions are fine with just a one-line description, if the description is written well. (The above example would probably be better off with just a single line.)

The description should be an imperative sentence stating what the function will do. The one above doesn't say "This function mocks the user" or "Mocks the user mercilessly". It just says "Mock the user".

Add a documentation string to the answer_phone function and try asking for help again.

Q3. Add a docstring to your compare function.

It is very important to choose good names for your functions. A well-chosen name is short and clear, and makes the function's purpose obvious. Function names should usually be verbs (as in answer_phone, not phone_answerer). A common exception to the convention of using verbs are functions whose primary purpose is to convert something (like int) or to extract a property of something (like len). But if a function has an effect (it modifies something, writes output, etc.), it should definitely have a verb as a name.

It's also important to choose good names for your variables, especially the function parameters. Variable names should be nouns. Ideally, someone should be able to tell what a function is for, why they would want to use it, and what arguments to pass in, just from the name of the function and names of its parameters.

3. Return Values

To return a value from a function, just say return followed by what you want to return. You can combine function calls in expressions in the normal way; each function evaluates to the value you return.

>>> def square(x):
...     return x * x
>>> square(5)
>>> square(5) + square(8)
>>> square(square(square(7)))

A return statement has the effect that it ends the function immediately. You can also use return by itself to exit out of a function without returning anything.

Q4. What about functions that don't contain return statements? What do they evaluate to?

4. Local and Global Variables

Variables defined at the top level, outside of functions, are global. Inside a function, you can have a set of local variables that are separate from the global ones.

Try this:

def test():
    y = 2
    print x
    print y

x = 1
print x
print y

print x
print y

Q5. What happened? What's different about the variables x and y in this example?

Now try this:

x = 9

def test():
    print x

print x

and compare that to this:

x = 9

def test():
    print x
    x = 1

print x

Q6. What happened? How did Python decide to treat x differently in the last two examples?

It's totally important that you get this. If you are uncertain what is happening here, read the hint.

To force a variable to be treated as a global, you can use the global keyword. Try this:

x = 9

def test():
    global x
    print x
    x = 1

print x

When you get here, stand up, stretch, maybe walk around the room. Just get out of your chair for a moment.

5. Nested Functions

You can define functions within other functions, if you want. Each new level gets its own set of local variables.

Here is a classic example:

def make_adder(x):
    def adder(y):
        return x + y
    return adder

Q7. What does the make_adder function do?

Try these, and make sure you understand the results:

addthree = make_adder(3)

Consider this function:

def t(f):
    def g(x):
        return f(f(f(x)))
    return g

Q8. Describe what t does.

What does t(square)(7) return? Figure out the answer before you try it.

Maybe you've never seen a language that could do anything like this before. If this is all totally weird to you, call me over and i'll try to explain.

6. Comparisons

Any object can be compared (using <, >, etc.) with any other object in Python. Even two objects of different types can be compared.

Try compare(3, '3'). (If the output looks silly, put one of your shoes on top of your monitor. Then fix your compare function.)

Try these:

compare(3, '5')
compare(3, '1')
compare(3, '')
compare(1, 1.0)
compare(1, 0.5)
compare(1, 1.1)
compare(10/3, 3.3333333333333335)
compare(10/3.0, 3.3333333333333335)

Q9. How does Python compare numbers and strings?

7. Formatting

You can also use the % operator on strings. When the first operand to % is a string, the string is considered a formatting template and the second operand is the value to put into the string.

Formatting templates are like normal strings except that the '%' character is special. For example, if you write '%d' then an integer value is inserted. Here are the most commonly used codes:

%d      decimal integer is inserted
%f      floating-point number is inserted
%s      string is inserted
%r      repr() of the value is inserted
%x      hexadecimal integer is inserted

You can also specify a width (in characters) immediately after the '%'. If the width is positive, the value is right-justified, and any extra space is filled with spaces. If the width is negative, the value is left-justified. For a numerical format, if the width begins with a zero, the padding is filled with zeroes. For a floating-point number, you can specify the precision as well: put a dot after the width, then the number of decimal places.


'%d' % 5
'%10d' % 5
'%-10d' % 5
'%f' % 5
'%.1f' % 5
'%9.2f' % 5
'%s' % 'hello'
'%40s' % 'hello'
'%-40s' % 'hello'

You can also specify more than one value:

>>> 'I went to the %s to buy %d eggs and %d cans of spam.' % ('store', 5, 7)
'I went to the store to buy 5 eggs and 7 cans of spam.'

In this last case, the second operand to % is what's called a tuple (an array) containing three values. We'll talk more about tuples later.

This operator can be pretty convenient in print statements.

8. Modules

Try this:

>>> import time
>>> time.asctime()
'Wed Jan 29 02:30:44 2003'

You just loaded a module called time that provided a bunch of other functions. One of the functions in the time module is called asctime().

When you load a module using the import statement, it is like making an assignment to the name you've imported. The variable time now refers to the time module. Try printing out time, or asking for the type() of time.

Each module has its own namespace (its own set of variable names associated with values). You can access the module's contents using the module name followed by a dot and the name of something inside the module. (This is a lot like Java, if you're familiar with that.)

To see what else is in the module, try dir(time). You can use dir() on just about any object. It will tell you what names are in the object's namespace – that is, what you can put after the dot. (You will see a lot of entries that start with double-underscores; they are special. Don't worry about those for now.) dir() with no arguments shows you the names in the namespace you're currently in. Try that too.

Any ordinary Python file can be a module. Try putting this in a file called counter.py:

print "Hello!"

x = 0

def count():
    global x
    x = x + 1
    print x

Now as long as you're running Python in the directory where you saved this file, you can import counter and call counter.count() or look at the value of counter.x.

Notice that when you import a module, all the code in the module runs normally. So, for example, importing this module prints Hello! to the screen. After the module finishes executing, it leaves its namespace around so you can use whatever useful things are in it.

It is usually bad form for a module to cause effects upon importing, like this one does. You should write modules so that importing them defines things for later use, but doesn't actually take actions. Initialization work is okay, as long as it's quick. Anything more involved should be in a function; the person using the module can call that function when they're ready.

Python comes with tons of modules. Remember the module index? You can run help() and type modules to get a list of all the modules. The standard library is filled with wholesome Python goodness!

9. Sequences

Have a closer look at the thing returned by dir().

>>> import math
>>> dir(math)
['__doc__', '__file__', '__name__', 'acos', 'asin', 'atan', 'atan2', 'ceil',
'cos', 'cosh', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp',
'log', 'log10', 'modf', 'pi', 'pow', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']

This is a list of strings. A list is one kind of sequence. A string is also a kind of sequence. len() works on both. To get a single element out of a sequence, you use square brackets surrounding a number (an index into the sequence). Index numbers start at zero; the index number for the last item in a sequence is the length of the sequence minus 1. If the index number is negative, it counts backwards from the end of the sequence. So another way to get the last item of a sequence named x is to say x[-1].

Instead of getting a single item, you can also cut a subsequence out of a sequence. This is called slicing a sequence, and you do this by putting two numbers in the square brackets, separated by a colon. If you put the colon but leave off the number before or after the colon, the slice will extend to the beginning or end of the sequence.

>>> x = dir(math)
>>> len(x)
>>> x[8]
>>> x[:8]
['__doc__', '__file__', '__name__', 'acos', 'asin', 'atan', 'atan2', 'ceil']
>>> x[-5]
>>> x[-5:]
['sin', 'sinh', 'sqrt', 'tan', 'tanh']
>>> x[10:13]
['e', 'exp', 'fabs']
>>> y = 'prestidigitation'
>>> y[8]
>>> y[:8]
>>> y[-5]
>>> y[-5:]
>>> y[10:13]

Notice that the result of slicing y[10:13] begins with y[10] and ends with y[12]. To keep this straight, just remember the rule that x[a:b] has the length b - a.

Q10. How do you get "git" out of "prestidigitation"?

Q11. What happens if you index a sequence using a number that's too big?

Q12. What happens if you slice a sequence using a number that's too big?

Q13. How about when you slice a sequence and the starting index is bigger than the ending index?

You can add lists together (try it) and multiply lists by numbers, too.

The way to gather things in a list is to start with an empty list:

>>> froofroo = []
>>> froofroo

and then call the append() method to append things to the list:

>>> froofroo.append(3)
>>> froofroo.append(5)
>>> froofroo
[3, 5]

We'll talk more about methods later, but for now, think of it as a function attached to the list. It's a lot like a function within a module (and that's why you write it the same way), except that the function affects this specific list: if you had other lists, of course, calling their append() methods would affect each of them.

All right! Onward to the second assignment.

If you have any questions about these exercises or the assignment, feel free to send me e-mail at bc@zesty.ca.