Introduction: functions, abstraction, style

Computer science is about solving problems, preferably in a beautiful and artistic way. Computers are used in every discipline from medicine (medical robotics, analysis of the spread of disease, design of drugs) to the visual arts – I’m sure you’ve seen a Pixar film recently. Computer science is one of the newest disciplines; it has completely transformed our world and will continue to do so.

Above all else, computer science is a creative discipline, mixing some of the best aspects of engineering, art, science, language, and mathematics. Learning about computation will change how you view problems in every discipline, and how you think about solutions to those problems.

Python shares many similarities with other programming languages, including Java and C++, and once you’ve learned Python, these other languages will not be difficult to pick up. Python is a fairly popular language, and it’s also fairly easy to get started with.

Let’s get started. A computer program is made up of instructions that a computer follows. Here is a simple program with one instruction:

This program causes the computer to print the word Hello in the console window. Notice that the quotation marks around Hello are not printed.

Here’s another program:

In this multi-line program, commands are executed in the order they appear.

As the Python interpreter runs a program, the interpreter keeps track of the line number that is currently being executed. The bookmark used to keep track of the line number is called the program counter.

The program counter always starts out pointing at the first line of the code in the program you run. It executes that line of code. Depending on what that line of code says, one of two things happens:

  1. The program counter increases by one and the interpreter executes the next line of code
  2. OR the line of code instructs the interpreter to jump to some other line of code, and sets the program counter appropriately.

Why would we ever want the interpreter to suddenly jump to a new line of code? We’ll see many reasons, but the first is to write and use our own commands, called functions, that are made up of a chunk of lines of code.

Functions

By giving a name to a chunk of lines of code, we can easily re-use that section of code without having to retype all those lines every time. Here’s a program that defines our own two functions, say_hello and why_goodbye.

Notice that nothing is printed when you run this program. We have instructed Python that there are two functions, and described them, but have not actually instructed Python to run either of the functions.

Function definitions have two parts: a header, and a body. The header gives the name of the function you want to define (as well as some other things we’ll see later). The body is a set of lines of code that tell what the function does. Each line of the body must be indented by pressing the tab key. The indentations tell Python where the function body begins and ends.

The header uses the keyword def (short for define), the name of the function you’d like to define, parentheses after the name of the function, and a colon. So the header for the first function is:

def say_hello():

The body of the first function is

print("Hello.")    

To tell the interpreter to actually run the body of a function, you use a function call. To write a function call, write the name of the function followed by parentheses.

When the program counter reaches a function call, the interpreter takes a note of where the program counter is, moves the program counter to the first line of the body of the appropriate function definition, and executes the lines of code in the function. When the interpreter gets to the end of the body of the function, the program counter jumps back to just past the function call, and Python continues from there.

Let’s trace out the program counter’s locations as the above program executes:

  1. The program counter advances past the function definitions, to the first line where a function is executed: say_hello().
  2. Then the function say_hello is called.
  3. The function call causes the program counter to jump to the first line of say_hello, which is print("hello") This line causes hello to be printed.
  4. The program counter then gets to the end of the body of say_hello, and so it jumps back to just past the call of say_hello, which is the line containing the second call of say_hello.
  5. The function call causes the program counter to jump to the first line of say_hello, which is print("hello") This line causes hello to be printed.
  6. The program counter then gets to the end of the body of say_hello, and so it jumps back to just past the second call of say_hello, which is the line containing the call of why_goodbye.
  7. The function call causes the program counter to jump to the first line of why_goodbye, which is print "I don't know why you say goodbye." This line causes I don't know why you say goodbye. to be printed.
  8. Then the program counter gets to the end of the body of why_goodbye, and jumps back to the line containing the call `print(“I say hello.”)
  9. I say hello. is printed. But this call to print was the last line in the Python program, and so the program terminates.

It is very important to understand the order in which Python executes lines of code. Stepping through code one line at a time in your mind in the order that Python executes those lines is a very good habit to get into.

Exercise: hello, hello!

Objective: Write and call your own functions to learn how the program counter steps through code.

You will write a program that introduces you and prints your favorite number.

  1. Write the body of the function print_favorite_number
  2. Write a function call at the end of the program that calls print_favorite_number.
  3. Write a new function, say_introduction that prints Hello, my name is X. and I am a Y. on two separate lines, where X and Y are replaced by whatever you like.
  4. Add a line that calls say_introduction so that the introduction is printed before the favorite number.

Test your code after steps 2 and 4: take a minute to think through what you expect to happen, and then press the run button.

Here is a solution. No peeking until you have solved it yourself!

Abstraction

Building higher-level concepts from simpler pieces, and then working with those higher-level concepts, is called abstraction. Abstraction is a major tool in computer science that we’ll come back to over and over.

Abstraction hides the details of how things work. Functions are our first example of abstraction. We defined a few functions, each in terms of a sequence of simpler commands. Once we defined those functions, we were able to just use them without thinking (too much) about how they worked.

Abstraction is not just important in computer science. When I press the gas pedal on my car, a lot of stuff happens, but because I have the higher level “push-gas-pedal” concept, I don’t have to think about the details of what is going on in the internal combustion engine to make my car go.

It might be fun to consider any other hobby or academic discipline you are interested in and explicitly identify the uses of abstraction, and levels of abstraction.

Calling a library function

Here’s another big win for functions. You can use a function someone else wrote, without knowing exactly the internal details of the function. Let’s look at an example:

The first line says that this program will use the print_date_and_time function from the simplefunctions library of functions. (In Python, such a library is called a module.) The program then calls the print_date_and_time function. When this function is called, the interpreter saves the current value of the program counter, sets the program counter to the beginning of the print_date_and_time function somewhere in the simplefunctions module, executes the code it finds there, and then resets the program counter to the original, saved value.

Functions that take parameters

Just like the print command, functions can take parameters – additional information that influences the results of calling the function.

When making a function call, we write the values of the parameters inside the parentheses. We say that we pass the parameters to the function at the point of call. Functions may take multiple parameters, in which case we separate them by commas when we call the function. Here’s an example:

There are a few things to notice. First, there’s the import command. This first line says “go to the cs1lib_web module; we’ll be using the clear, draw_circle, and start_graphics functions from there.”

Then we define the draw function. This function isn’t executed quite yet; we are just defining it. Python doesn’t even look inside the function body yet. Instead, Python jumps to the line start_graphics.

The function call start_graphics does two things:

  1. It opens a window to draw the graphics.
  2. It calls some other function, specified by the parameter to the start_graphics.

We never actually have to call the function draw ourselves in this case. Instead, we passed the name of this function as a parameter to start_graphics, and that function runs draw. Yes, the parameter that we pass to start_graphics is not a number, but rather a function. start_graphics expects to be given a function as a parameter.

What do we know about start_graphics? We know that it enables us to do graphics by opening a window on which graphics appear, and we know that it runs a function that we pass to it as a parameter. We don’t know how it does what it does, however. Abstraction at work!

Let’s look at what happens when start_graphics gets around to calling draw. First, draw calls the function clear, which clears the window.

Second, draw calls the function draw_circle, passing it three parameters. The first parameter, 125, says to draw the circle at x coordinate 125 in the window. The second parameter, 100, says to use the y coordinate 100. The third parameter, 50, says that the radius of the circle should be 50. The final line of the draw function calls the draw_circle function again, with different parameters.

It is very important to understand the order in which Python executes lines of code. Here is an exercise. Grab an index card or piece of paper, and place it under the first line of code in the program. Move the card in the same way the program counter would move. First, the import statement. Then, the line def draw. After that, jump past the body of the draw function, since this is just the definition of the function. Jump all the way down to start_graphics. We know that this function calls draw. So eventually, we jump back up to the first line of the body of draw, and step through the lines in draw.

Stepping through code one line at a time in the order that Python executes those lines is not very exciting, but a good habit to get into. You should step through every piece of code you see as you are learning Python.

Graphics coordinates

We glossed over what the coordinates passed to the draw_circle functions really mean. When we open a window for graphics, there’s a coordinate system within the window. Coordinates correspond to pixels in the window, with (0, 0) at the upper left corner of the window:

The coordinates (125, 100) mean the pixel that is 125 pixels to the right and 100 pixels below the upper left corner. The above diagram shows a rectangle with a black outline one pixel thick and a red fill, with its upper left corner at (3, 1) and its lower right corner at (9, 5).

Think of it this way: starting from the upper left corner of the graphics window, x coordinates increase to the right, and y coordinates increase down from the top. If you think about how x and y coordinates usually work in mathematics, graphics on a computer doesn’t quite match up. The x direction works, but in math, increasing y coordinates go up, whereas in computer graphics, increasing y coordinates go down.

What do the parameters passed to draw_circle mean? The first two parameters give the x and y coordinates of the circle’s center, and the third coordinate gives the circle’s radius in pixels. Here’s a simple exercise for you to try: What would you see if you called draw_circle(0, 0, 100)?

Exercise: smile

Objective: learn how to call drawing library functions that take parameters, in order to draw a desired complex picture.

Write a program that causes a yellow smiley face to be drawn on the screen. You have a 200 by 200 window available.

  1. Draw the outline of the face. Put your code for drawing the outline after the line # draw the outline of the face. (Lines that begin with # are ignored by the computer, and are for human readers only.

  2. Draw the eyes. Put your code for drawing the eyes after the line # draw the eyes.

  3. Bonus challenge. Draw the mouth. Hint – draw a circle, and then erase the top part of it by drawing some sort of yellow shape.

Here is a solution. No peeking until you have solved it yourself!

Good coding style

Computer programs are not just for computers. Good code should do two things:

  1. It should allow the computer to solve a useful or interesting problem.
  2. It should describe a solution to the problem in a way that a human reader can understand it.

A well-written program is more than a machine for solving a problem – it is in a real sense a description of a solution to a problem. How to write good, human-readable code is a major focus of this course, and we’ll come back to it again and again.

Let’s look at the following bad program.

What does this program do? Does it describe how to solve a problem? What’s the problem? Is this is a good solution? Could you modify it easily to solve a more interesting problem, without even knowing what the program does?

In this case, the program is short and you could figure out what it does by running it with Python. It would still be a lot of work to figure out what each line of code was intended to do, although we might get a high-level picture from Python.

(One sidenote: from cs1lib import * tells Python that you want to be able to use every function from the cs1lib module, freeing us from having to specify each and every function we intend to use.)

Explaining your code with comments

You can use the # symbol in Python code to indicate code that Python should ignore. On a line of code, everything after the pound symbol is called a comment, and is ignored by Python – it’s just there for human readers.

Here’s an example of the above program with comments, whitespace, and a better function function name.

We can now see exactly what the program is intended to do, and also which lines of code accomplish which parts of the solution.

How much should you comment your own code? Here are some guidelines:

  1. Always put a comment at the top of the program or file describing the overall intent and how the program works.
  2. Always put a comment before the beginning of each function you define, indicating the purpose.
  3. Always comment something that might be mysterious even to an experienced programmer

Good comments add new information, but that does not mean you should comment every single line of code. Here’s a distracting comment that is not useful:

draw_rectangle(280, 310, 25, 40)     # draw a rectangle

We can see from the function name that this line draws a rectangle. A more useful comment would tell us what we are trying to do with the rectangle (in this case, draw a door). Another comment might tell us the fill color:

# draw the door
set_fill_color(.6, .5, .3)  # light brown
draw_rectangle(280, 310, 25, 40)

Functions and style

Organizing code into function definitions and function calls is an example of abstraction, and also is an example of good style. It’s probably not hard to guess that the function draw_rectangle draws a rectangle. We could have drawn the rectangle by drawing four individual lines using four function calls, but it’s better style to use a single, appropriately specific function call.

In the previous example, we saw a few new graphics functions:

Using whitespace well

Python ignores certain types of whitespace, such as blank lines and spaces after commas. You should use blank lines to group your code into logical segments. A good rule of thumb is that there should be a blank line roughly every 3–10 lines of code. If you have fewer blank lines, look hard to see whether you can break down your code into smaller chunks. You can think of these blank lines as serving the same purpose that new paragraphs do in English writing.

It’s also good style to put a space after every comma, just as you would in written English.

Bear in mind that whitespace at the beginning of a line is very significant. In Python, indentation indicates structure. You’ve already seen how the body of a function is indented by one tab stop. We’ll see plenty of examples of indentation as we go along.