CIS552 Course Transcript

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 173

User-Defined

Functions in Python
What you'll do

Identify how functions work


Build a technical vocabulary
Visualize Python executions
Follow rules for writing functions
Recognize a properly formatted specification
Identify strategies for effective debugging
Turn an English description into code
Read and interpret error messages
Write an informative error message

Course Description

A core element of a Python program is the use of


functions: the actions they initiate and the information
they organize and simplify. Because functions are
multipurpose, you need to decide the role they should
play in order for your program to run as intended.

You already know how to call functions. In this course, Cornell University
Professor Walker White helps you build the skills to not only create your
own functions but also develop a full understanding of functions in a
business context. You will write specifications and examine what makes
a good or bad spec. You will perform testing, debug code, and look at
error messages. Has a program ever given you a confusing error
message? Before you've completed this course, you will identify and
write helpful error messages.

This course continues the skill and knowledge-building that are essential
for Python programming in a business setting.

System requirements: This course contains a virtual programming


environment that does not support the use of Safari, IE, Edge, tablets, or
© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
mobile devices. Please use Chrome or Firefox on a computer for this
course.

Walker M. White
Senior Lecturer and Stephen H. Weiss
Provost's Teaching Fellow
Cornell Computing and Information Science,
Cornell University

Walker White is a senior lecturer and Stephen H. Weiss


Provost's Teaching Fellow in the computer science department. He has
designed the introductory computer science courses that serve as an
inspiration for this course.

Walker White is also the Director of the Game Design Initiative at Cornell.
In this role, he directs the computer game minor at Cornell and teaches
the primary game design courses. He is a strong proponent of project-
based education incorporating interdisciplinary teams, and he is the
faculty sponsor of the CU-AppDev engineering project team.

Table of Contents

Module 1: Executing Functions

Module Introduction: Executing Functions


Activity: Explore the Codio Project
Watch: Reviewing Basic Terminology
Checking Vocabulary
Watch: Defining a Procedure
Watch: Defining a Procedure in a Module
Activity: Define a Procedure
Watch: Defining a Fruitful Function

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Define a Fruitful Function
Watch: Visualizing a Function Call
Read: Procedures vs. Fruitful Functions
Watch: Visualizing Frames in the Python Tutor
Activity: Explore the Python Tutor
Visualizing a Function Call
Watch: Accessing Global Variables
Activity: Access Global Variables
Watch: Calling Functions from Functions
Activity: Break Up Functions
Visualizing the Call Stack
Watch: Defining Optional Arguments
Activity: Create Optional Arguments
Tool: Fundamental Python Concepts 1
Project Submission for Executing Functions
Module Wrap-up: Executing Functions

Module 2: Specifying Functions

Module Introduction: Specifying Functions


Watch: Motivating Function Specifications
Motivating Function Specifications
Watch: Writing Specifications
Reading Specifications
Watch: Specifying Preconditions
Watch: Identifying Types of Preconditions
Identifying Preconditions
Watch: Recognizing Bad Specifications
Tool: Fundamental Python Concepts 2
Identifying Bad Specifications
Module Wrap-up: Specifying Functions

Module 3: Testing Functions

Module Introduction: Testing Functions


Watch: Identifying Errors
Reviewing Terminology
Watch: Designing Test Cases

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Designing Test Cases
Designing Tests with Multiple Arguments
Watch: Scripting Tests
Activity: Script Tests
Read: IntroCS Documentation
Watch: Organizing Test Cases
Activity: Organize Test Cases
Watch: Debugging Discovered Errors
Activity: Debug Discovered Errors
Tool: Fundamental Python Concepts 3
Project Submission for Testing Functions
Module Wrap-up: Testing Functions

Module 4: Designing Functions

Module Introduction: Designing Functions


Watch: Motivating the Implementation Process
Watch: Implementing with Function Stubs
Activity: Implement with Function Stubs
Watch: Implementing with Pseudocode
Implementing with Pseudocode
Activity: Implement with Pseudocode
Watch: Testing with Function Stubs
Watch: Implementing Backwards
Activity: Implement Backwards
Watch: Implementing with Helper Functions
Activity: Implement with Helper Functions
Tool: Fundamental Python Concepts 4
Project Submission for Designing Functions
Module Wrap-up: Designing Functions

Module 5: Enforcing Specifications

Module Introduction: Enforcing Specifications


Watch: Reading Error Messages
Watch: Interpreting Error Messages
Activity: Interpret Error Messages
Watch: Asserting Preconditions

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Assert Preconditions
Watch: Designing Error Messages
Activity: Design Error Messages
Watch: Identifying Trade-offs
Watch: Enforcing with Helper Functions
Activity: Enforce with Helper Functions
Tool: Fundamental Python Concepts 5
Project Submission for Enforcing Specifications
Module Wrap-up: Enforcing Specifications

Course Project

Programming with Functions


Read: Thank You and Farewell

&RXUVH5HVRXUFHV
*ORVVDU\

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module 1: Executing Functions
Module Introduction: Executing Functions
Activity: Explore the Codio Project
Watch: Reviewing Basic Terminology
Checking Vocabulary
Watch: Defining a Procedure
Watch: Defining a Procedure in a Module
Activity: Define a Procedure
Watch: Defining a Fruitful Function
Activity: Define a Fruitful Function
Watch: Visualizing a Function Call
Read: Procedures vs. Fruitful Functions
Watch: Visualizing Frames in the Python Tutor
Activity: Explore the Python Tutor
Visualizing a Function Call
Watch: Accessing Global Variables
Activity: Access Global Variables
Watch: Calling Functions from Functions
Activity: Break Up Functions
Visualizing the Call Stack
Watch: Defining Optional Arguments
Activity: Create Optional Arguments
Tool: Fundamental Python Concepts 1
Project Submission for Executing Functions
Module Wrap-up: Executing Functions

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Introduction: Executing Functions
You've explored how to call functions in Python. Now
we're going to look more closely at the details that go into
coding functions. You'll review important Python
terminology and examine how to write both a procedure
and a fruitful function. In addition, you'll be introduced to
the call frame and practice your visualization skills.

The recommended version of Python to use during this course is


Anaconda. If you do not already have it on your machine, the installation
link is https://www.anaconda.com/.

The recommended code editor is Atom, which can be downloaded at


https://atom.io/. If you encounter issues with either application and are
unable to complete the installation process, contact your instructor for
assistance.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Explore the Codio Project
Many of the assessments in this course module will take place in a Codio
project. Furthermore, many of these assessments build on each other,
requiring you to copy over files. To simplify this process, we have created
a single Codio project for the entire course project. It contains all the
sample code for this course module as well as many of the exercises.

Read the text below to familiarize yourself with the structure of this
project. Once you are finished, feel free to explore some more. However,
do not work on the exercises until you are directed to.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Reviewing Basic Terminology
As you learn to code and then design functions in this course, there will
be many terms you'll need to understand.

In this video, Professor White uses the Terminal to introduce some


Python terms in context in order to help you learn them more effectively.
To view the glossary for this course, click in the course navigation menu,
then .

Video Transcript

We're going to introduce the basic terminology that you're going to need
to know in order to start coding functions. Now, when we talk about
coding functions, we mean this in a very narrow context. We're not
talking about designing functions, which is really the larger goal of this
entire course. In designing functions, you would be writing your own code
with very little guidance. In this case, we're just going to really be focused
on the technical details of how do you write functions in Python in such a
way that you don't get error messages? Even to do that little, we're going
to have to introduce a lot of terminology. And if you don't know this
terminology, you're going to have a very hard time following these
lectures. For that reason, we are going to have a glossary available on
the Canvas course web page so that you can look up these terms at any
given time. But in this video, it's going to help for you to see a lot of these
things in context to help you understand what these words are. In
addition to introducing new terminology, we're also going to try to
standardize some other terminology. One of the problems with
programming is that sometimes people use very similar words in very,
very different ways, and we're going to have to be very, very precise with
our language. So right away, we're going to assume that you are familiar
with what is known as a function call, even if you don't know what that
term is.

A function call is what it's known in Python when you use a function in
your code. To call a function, you first type its name. So in this case, what
I'm going to do is I'm going to use the function round(), and I'm going to

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
type its name, "round". That by itself is not a function call; I also have to
give the function something to operate on. So I'm going to give it some
parentheses, and then inside of the parentheses, I'm going to give it a
value; "(26.54)". This is the function call: the name, plus the parentheses,
plus the expression inside the parentheses that it can use in order to do
its work. I'm going to hit Return, and Python has rounded this number,
and it's given me the value 27 back. Those things that are inside of the
parentheses, they also have a very formal name; they are known as
arguments. So in this case, 26.54 is the argument for my function call
round(). One of the interesting things about the function round() is that it
can actually take multiple arguments. If I take this function call round(),
and now I'm going to add a second argument, separating it by a comma
before I close parentheses. I hit Return, and now you'll notice that instead
of rounding to the nearest integer, Python has rounded to the nearest
tens place. And that's because I've given it the second number, telling it
the place after the decimal point for it to round.

Most of the functions that we work with are known as expressions. What
is an expression is just something in Python that when I type it into
Python, Python turns it into a value. That's what we saw right away with
round(). This is an expression; it's an expression that happens to be a
function call. And I hit Return, and it gives me back the value 27. The
nice things about expressions is that I can use them in assignment
statements; I can assign them to variables. So I can say, "x =
round(26.54)". Now, it didn't look like anything happened; that's because
assignment statements happen sort of silently or invisibly. But if I look at
the variable x, you'll notice that there is now a value 27 inside of x. We
bring this up because some function calls are not expressions; they are
instead statements. Let's look at another interesting function, print(). So
I'm going to print out the word 'Hello'. This looks a lot like what happened
with round(). It's a function call again; I had the name "print", and then I
have some parentheses, and then I've given it an expression inside the
parentheses. My argument in this case is the string 'Hello'. It looks like it's
given me a value back, but that's not true, because if you notice, that text
"Hello" doesn't have quotes around it; it's not a string. So it's unsure what
happened there. And in fact, let's try assigning this to a variable. I still see
the "Hello" again; it didn't actually run silently. And if I look inside of the
variable x, you'll notice that there's nothing inside of that variable right
now. That's because print() doesn't produce a value; it displays text on

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
screen.

Sometimes that can be confusing, because Python also can display an


answer on screen, so how can I tell one from another? In this case, the
easiest way is, well, what happens when you assign it to a variable? I've
assigned it to a variable, and there's nothing there. These second type of
functions, the ones that can be used to statements but are not
expressions, are what are known as procedures. So all procedures are
functions, but the reverse is not true. So, of course, that leads to the
interesting question of what do we call functions that are not procedures?
Historically, we used to call those functions so that we'd have two distinct
sets; we would have procedures that would be used as statements, and
then we'd have functions which would be expressions, and we could
assign them to values. But, this changed early on, because when we
started having the programming language C, it realized that sometimes
you have some things in a gray area. You might have a function that
does something but you might want to return it as well. So starting with C
and continuing with other languages like Java or C++ or Python, we now
use "functions" to refer to the entire group, including procedures, and for
a while, we didn't really have a term for those functions that are not
procedures. The term that we're going to be using in these videos are
what we call "fruitful functions." This is a really cute little term; we're just
sort of pointing out that, "Hey, this is a function that produces something,"
like producing fruit. This is not a term we've actually invented; this
actually comes from a very popular textbook called "Think Python" by
Allen Downey. And while we won't be using that textbook for this course,
it's a very nice term, and so it's the one that we will be sticking with.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Checking Vocabulary

&KHFNLQJ9RFDEXODU\FRGLQJH[HUFLVH


Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Defining a Procedure
Now that you've been introduced to procedures, the next step is learning
how to write a procedure.

In this video, Professor White demonstrates how to write a procedure


definition and points out some important terms that are part of definition
writing.

Video Transcript

Our goal throughout this entire module is to teach you how to write a
function definition. You know how to call a function. And Python does
something when you call a function. But how does Python know what to
do? Well, the built-in functions, they have definitions that tell Python what
to do. They're hidden; you'd have to go search for them if you wanted to.
But in this video, what we're trying to do is to teach you how to write your
own functions, so you're going to have to write your own definitions. Right
now we're just going to focus on procedures. That's because procedures
are the easier of the two function types. With that said, most of what we
say in this video applies to all functions. So right away, let's just look at a
function definition. In this file I have open in front of me, I have a
definition for a function called greet(). Right away you'll notice that it
starts with the word "def", which stands for "definition." This is a keyword,
and when Python sees this, it knows that I'm beginning a definition. The
rest of this line is what is known as the function header. And one of the
key important things about the function header is that it has to end in a
colon; if you don't have it ending in a colon, you're going to get an error
message.

After the header, we have several lines that compose what are called the
function body. The most important thing that you notice about the
function body is that it doesn't quite line up with the "def" keyword;
actually, all of these lines are indented. That's because that's how Python
tells where a function begins and ends. If I were to type another line, like,
say, another print statement, in this case you'll notice that the print
statement is now aligned with the keyword "def"; it's no longer indented,

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
so Python knows that it's not part of the body. The only thing that is in the
body is what is indented right now. Let's look a little deeper at this
function body. So, the function body breaks up into two parts right here.
One of the things you'll notice that I have several lines that are between
two sets of triple quotes. This is a docstring, or a multi-line comment. It's
very, very similar to the docstring that I have up at the top of the file.
Docstrings are usually a way of writing notes to myself. This particular
docstring, one that is attached to a function, has a very specific name. It's
what's known as a specification. And specifications will be an important
topic of another module.

After the specification, I have several lines; each one of these is a


statement for Python to execute. You notice that I have an assignment
statement, and then I also have print(), which is a function call which can
be executed as a statement. Now let's go back to the header. You'll see
that, of course, we have the keyword "def". After "def" what we have is
we have the word "greet". This is the function name. Functions can be
named whatever I want. The rules for naming a function follow exactly
the same rules for naming variables. After that I have a set of
parentheses. Inside of this parentheses, you'll notice that I have a
variable; the variable n. This variable has a very specific name; it is what
is known as a parameter. Parameters are variables that are listed inside
of the parentheses of a function header. This is important because this
tells me how to call this function. If I wanted to call this function, what I
would do is I would type the name of the function, which is "greet",
parentheses, and now I'm going to give it an argument.

Let's say I want to greet myself. In this case, I've given it a string as an
argument. I gave it exactly one argument, and the reason why I gave it
exactly one argument is that I have exactly one parameter here. So I
must have enough arguments to match the parameters in my function
definitions. So let's see how we use this function. I'm not actually going to
work with a file right now; instead I'm going to work directly into Python,
and I'm just going to cut and paste this definition into Python. You'll notice
that actually when I type this definition into Python, I'm not seeing the
three greater-thans anymore; it's seeing these dot-dot-dots. It's because
Python knows that I'm in the middle of a function definition and it's waiting
for me to finish the definition before it can continue. Well, the way you've
finish a function definition is you just type in a line that isn't indented. I hit

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Return, and so now I have defined the function greet(). So I can write
away; I can now call it. And notice that it greeted me.

What happened in this case? Well, what Python did was it took the
argument — in this case, 'Walker' — and it plugged it inside of the
variable n. Then it executed each line, so, in fact, what we have here is
an assignment statement that adds my name to the word "Hello" and
puts it inside of the variable text. And then finally what it did was it printed
out that text, and indeed that's what you saw happen on the screen. One
of the important things to understand, however, is that you have to type in
a function definition before you can call it. If we were to quit Python, so
that we forget everything. And now I'm going to try to greet myself. Now
Python doesn't know what to do because it doesn't actually have a
function definition. Once again, I'm going to have to paste into the
function definition. And now I can call the function, and it works normally.
As one last bit of terminology, I just want to look at this variable text right
here. We call the variable n a parameter. This is also a variable, but since
it's not inside of parentheses we give it a different name. This is what is
known as a local variable. And in fact, when we start going deeper into
function definitions, keeping parameters distinct from local variables is
going to be an important topic.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Defining a Procedure in a Module
While this course contains modules, the term used by Professor White
refers to a file that contains Python code. As you may have found, it can
be tiresome to keep typing function definitions into Python. This is why
using modules is so helpful. In this video, you'll examine how modules
can simplify function definitions and the role they will play in your work
throughout this course.

Video Transcript

Typing function definitions directly into Python can get a little annoying,
particularly when we start to make mistakes. That's why what we like to
do when we write function definitions is to write them in files; in particular,
modules. Now, to remind you, a module is a file that has Python code in
it. Modules typically end with the suffix .py, and you edit them with a code
editor. The code editor that I'm going to be using through my
demonstrations is Atom Editor. Right now on the screen I have Atom
Editor open, and it's open to a module. This is a very, very simple
module; it has a docstring at the beginning, which is sort of a comment to
myself; it also has a single-line comment; and it just has two assignment
statements in it. To use a module, I have to import it. Of course, the first
thing that I have to do is I have to make sure that I'm running Python in
the correct directory. So this particular file, module.py, is inside of a folder
that I call unit2. I'm going to have to open up the Terminal window, make
sure with "pwd" that I am inside the right folder before I continue.

And now I can start up Python, and now I'm going to import the module.
When you import a module, what it does is it's going to execute every
single statement in that file. In particular, it's going to execute the two
assignment statements here for the variable x. And now, from this point
on, I can access that variable if I want to. The way that I access that
variable is I type the name of the module, period, and then the variable x,
which is what I want to access. Now, modules also allow you to access
functions. By now you should be familiar with some basic Python
modules; for example, say, the "math" module. So I can "import math".
"Math" has a variable pi inside of it which I can access, which works very

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
much like the variable x that we just saw for the first module. But it also
has functions inside of it that I can call. Again, I would type the name of
the module, which is "math", and then dot, the name of the function. And
now I call the function with parentheses, giving it a value of 0.5, and I get
an answer back.

What's going on here? Well, actually, there is somewhere on your system


a file called math.py, and inside of that file is a definition for the function
cosine(). What happens when I import the module "math" is Python reads
that file, it reads the definition, and from that point on I can actually call
the function. So, for example, I have a module here which I call proc.py,
standing for "procedure", and once again it has the definition for my
function greet() in it. I'm going to "import proc". And now I can call
proc.greet(), and I'll give it an argument, my name, and it will do the
greeting. Remember that I always have to put the name of the module
before the function call. If I just call greet() by itself, Python won't know
where to look for the function definition. The function definition is
specifically provided by the module "proc", and therefore I always have to
preface my function call with that. So what we're looking at is sort of a
basic workflow for this module and, in fact, this entire course. What we're
going to be doing is writing procedures and functions in a module,
opening up the Terminal, moving to the directory that has this particular
module file, starting Python, importing the module, and then calling that
procedure or function.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Define a Procedure
In the Codio project below, navigate to the page Overview of Exercise
1. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 1, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Defining a Fruitful Function
Now that you've worked on defining a procedure, let's take a closer look
at the other type of function, a fruitful function. A fruitful function is a
function that terminates by executing a return statement.

In this first video, Professor White reviews the definition of a fruitful


function and demonstrates how fruitful functions produce values.

Video Transcript

In this video, we're going to show you how to define a fruitful function.
Before we do that, let's remind ourselves the difference, right? A
procedure is one where the function call is a statement. So if I were to
use print() and type "print('Hello')", this is a command to Python to display
the word "Hello" on the screen, but it doesn't produce a value. And if I
were to use this in an assignment statement like "x = print('Hello')", you'll
notice that there's nothing inside of the variable x. On the other hand, a
fruitful function is one where the call is an expression. If instead I were to
write an assignment statement with a fruitful function "round(26.54)", this
is going to produce a value. And when I look at the variable x, it now has
that value 27 inside of it. Now this seems like a minor difference, and it is
actually a minor difference. And for that reason, the definitions are almost
exactly the same. The only difference between a fruitful function and a
procedure is a minor change to the body.

Fruitful functions have a new type of statement in them, and this is what
is known as the return statement. In the code editor that I have here on
the screen, you'll notice that I have two functions for converting between
Celsius and Fahrenheit. So this is my definition of the function
to_centigrade(). Here, I have my keyword "def" to tell me that's the start
of a function definition, and I have my header. I have a very long
specification telling me a lot of details about this function, but I actually
only have one line of code inside of the body of the function. This is my
return statement. You can tell because right away it's starts with the word
"return". This is also a keyword; Python looks at this and it knows that
this line is supposed to be a return statement. After the return is an

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
expression. And what's going to happen is when I call this function,
Python is going to evaluate this expression after the return, and that is
the value that the function call is going to reduce.

Let's see this in practice. I'm going to import the module "temp". Now I'm
going to call temp.to_centigrade(); say, with 0 degrees. And we'll see that
that is -17 Fahrenheit. Similarly, if I look at to_centigrade() of 32, we'll see
that that is 0 degrees. At each time, what's happening is my argument —
in this case, 32 — is being plugged into the variable x and then the return
statement is processing that expression with that new value of x to give
me the final answer. Now, I don't just have to have a return statement by
itself; I could actually combine a return statement with other statements.
So let's look at another function that we have here, the function plus(). In
this case, you'll notice that in the body, I have two lines of code to
execute. I have an assignment statement that produces a local variable
x, and then in the end, I'm going to return the value of that local variable
x. I'm going to import this module. Now, I'm going to call this function
plus(); "plus(1)" is 2, "plus(4)" is 5. So at each time what's happening is
it's taking my argument, it's plugging into the variable n, it's executing
each line of code, and then at the end, it is returning the final answer. But
the key thing that you'll notice in each case is that the return statement is
always the last line of the function. That's because as soon as Python
sees this particular line, it stops executing.

Let's do an example. I'm going to add a print statement here at the end of
my definition. I'm going to save. I'm going to quit and start Python
because we have to do this anytime we make a change to a module. I'm
now going to "import fruit". And I'm going to call "fruit.plus(2)". You notice
that I get the answer of 3 but it doesn't actually print out "Hello." On the
other hand, if I were to put this print statement above the return; save,
quit, restart Python. This time we see the print statement. So, Python will
execute a body until it sees the return statement. An analogy to think of is
sort of like what you do when you're working on a math problem for an
exam. Typically, you do a lot of work on a sheet of scratch paper, and
then when you're done, you take a box and you use — or circle, and you
use that to circle your final answer. That's what the return statement is
doing; it's indicating what your final answer is. So once Python sees that
return, it's not going to do anything more.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Python beginners often confuse print statements and return statements.
In this video, Professor White shows what happens when you mix these
two statements up in a function definition.

Video Transcript

One of the problems that beginning students run into is they start to
confuse print statements and return statements. That's because, while
they are different, sometimes they can look a little similar. To show you
what we mean, I have a file open up here that has two function
definitions: print_plus() and return_plus(). You notice that these
definitions are very similar to one another. print_plus() ends in a print
statement, and return_plus() ends with a return statement. But in each
case, what we're doing is we're either printing or returning a number.
Let's watch what happens when we use each of these functions. I'm
going to import this module. If I call print_plus() on the number 1, looks
like I got the answer 2. If I call return_plus() on the number 1, once again,
I got the number 2. However, the key thing to understand here is that
only one of these two is a fruitful function. return_plus() is a fruitful
function because it has a return statement in it; whereas print_plus(),
because there's no return statement at all, is a procedure. How can we
tell this? Well, as always, the key is what happens when we put it in an
assignment statement. I'm going to call print_plus(), assign it to the
variable x; ah, you'll notice that it is displaying the number 2 on the
screen. But if I look at the variable x, there's nothing inside of that
variable. On the other hand, if I use return_plus(), this time the
assignment statement executes silently. And when I look inside of the
variable x, I have the value 2 inside of this. This is a very important
distinction, right? Return is necessary whenever we're trying to do
calculations. Print is not very useful for calculations; it's only useful when
we want to display things on the screen. In particular, one of the things
that we'll see is that print statement is very, very useful for testing.
Keeping these two things distinct from one another is going to be very
important to understand the types of functions that we're going to design
going forward.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Define a Fruitful Function
In the Codio project below, navigate to the page Overview of Exercise
2. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 2, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Visualizing a Function Call
A common ability of all skilled programmers is the ability to picture how
Python is functioning under the hood.

In this first video, Professor White uses a demonstration of a function call


to show why it is important to strengthen your visualization skills. If you
don't understand what Python is doing, it's going to be much harder to
understand what errors you're making in your code.

Video Transcript

Up until now you have a very informal understanding of how function


definitions and function calls work. But that's not good enough, right? To
be a professional programmer, you need to be able to learn to think the
way that Python does. Because if you don't understand what Python is
doing, it's going to be very hard for you to understand the errors in your
code. Good programmers can always see things from Python's
perspective. Now, the skill that we want you to develop is what is known
as visualization. In visualization, you're building imaginary models, so
what Python is doing underneath the hood while it's executing. We call
these "models" because they're not exactly accurate, right? We're not
expecting you to understand anything about your operating system or
how your hardware works. These are more like metaphors. But they are
very useful metaphors, and by using this technique you have a very good
understanding about what's going on in your code. To give you an
example of why this is so useful, let's look at a very simple function that
we have here.

This is our function plus(), it has a single parameter n, I have a body with
two lines of code with an assignment statement for a variable x and a
return statement. Let's take this definition and let's copy it directly into
Python. So now we can actually use this definition. Now I'm going to type
two lines of code. Let's type "x = 2", and then "y = plus(4)". Now, if I look
at y, that has the value 5; this is what we expect. But that's not actually
the interesting question; the interesting question is what happened to x?
In the beginning, I made this variable x, right, so that creates a box, and

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
that puts the number 2 inside of that box for x. But then after that I called
the function plus(). Remember what happens when we call a function;
what it does is it executes every single line of code in the body. Right
away we can see this line that does an assignment to the variable x.

So does that mean now that — OK, well, n is 4; I added 1 to it, that puts a
five. Does that mean that 5 is now inside of the variable x? Actually, no;
the variable x only has the value 2 in it. Now, why did that happen?
That's because I actually had two different variables here. This variable x
is what is known as a global variable. A global variable is any variable
that doesn't appear inside of the body of a function. This variable right
here is a local variable. It's inside of the body of the function. Because
one is a global variable and one is a local variable, these are two
completely different variables, even though they have the same name.
That can get really confusing, right; now we're talking about the prospect
where just because I know the name of a variable doesn't mean that I
know exactly what variable that I'm talking about. That is why we need to
build up visualization skills, and that is what we will address in the next
video.

In this video, Professor White introduces the call frame, a visual model
that helps show where variables are in relation to each other in Python.

Video Transcript

The visualization technique that we want to understand is the concept of


a call frame. A call frame is a representation of a function call; not a
function definition, but a function call. Now, this is essentially a model of
Python that's going to help us understand where our variables live. So,
the way that we're going to represent a call frame is essentially as a box.
Inside of this box is where we're going to put all of our variables; it's
where we're going to put our parameters, which are any functions that
are inside of the function header; and it's also where we're going to put
any local variables. In addition to sticking our variables inside of this box,
we're going to put two other things. To the top left corner, we're going to
write the name of the function; that's just a way to help us understand or
remember which function we're currently talking about. More importantly,
to the right, we're going to write the instruction counter. The purpose of
the instruction counter is it helps us keep track of what line of code we're

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
talking about at any given time. If we look in this file right now where I
have the function definition for plus(), you will notice that to the left, my
code editor displays several numbers. These numbers represent what
line of code I'm currently on. So, line 10 is the line that is referring to the
assignment statement, and line 11 is the line that refers to the return
statement. These are the numbers that we're going to be putting in the
top right corner of our call frame.

Let's just work through an example with this particular definition that I
have right here. So here's my definition, but what we're actually modeling
is a function call. So inside of Python I have a call for "y = plus(4)." So
what happens when we call a function? Well, right away what we're going
to do is we're going to draw our box for our call frame. In the top left
corner I'm going to write "plus", which is the name of the function. Inside
of it, what I'm going to do is I'm going to be writing the variables. But at
the very beginning, the only variables that exist are the parameters. Even
though there's a variable x inside of the body, I'm not going to worry
about x yet. But I am going to worry about the variable n because that is
a parameter. So I'm going to put a variable called n, and inside of the box
for n I'm going to put the number 4. And that's because that was the
argument that I gave inside of the function call. Now, it's important to
understand that the only things that can fit inside of variables are values.
If I were to call this differently, like if I were to say, "y = plus(3+1)", this is
not going to put "3+1" in the box for n. What it's going to do first is
evaluate "3+1" to the number 4, and then it's going to put 4 into the box
for n. The last thing that we do when we're constructing our call frame is
we have to give an instruction counter. The instruction counter is not the
current line of code; it is the next line of code. It's telling me where to go
next. And where I want to go next is right here. I haven't done line 10 yet,
we don't have a variable x yet, but it is the next line because it is the first
part of the body that is not a comment. And so that's generally what
happens whenever we create a call frame, is our instruction counter will
always be the first line of the body after the specification. OK; so that's
our initial picture. Where do we go from here?

Well, now what we're going to do is we're going to execute the body until
the end. We're going to process one line of code at each time, so what
we're going to do is each line in this function body, we're going to process
it. Each time that we do this, we're going to redraw the frame. We're not

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
creating a new frame; what's happening is the original frame is changing.
So what you should think of is that we are animating the frame over time.
As you go through, you're going to be processing each type of statement.
Right now we only know three types of statements: a print statement, an
assignment statement, and a return statement. If you see a print
statement, it's easy; there's no change to the frame, there's no new
memory or anything like that. It's just that it displays something to the
screen. And we don't need to visualize that because Python shows that
for us. If it's an assignment statement, then we're actually going to have
to put a variable inside of the call frame. And if it's a return statement,
we're going to have to do something slightly different. Well, all right; once
again, let's go step through our example. So we have our call frame, and
right now the instruction counter is at line 10. So that means it is now
time to process line 10.

I see that I have an assignment statement for x. Assignment statements


are always read right to left, so I take n, which is currently 4; I add one to
it, I get a 5; and now I create my variable x, which inside of the box for x
has the value 5. I don't get rid of n — it's still there — but I've added the
variable 5. In addition, what I need to do is I need to move the instruction
counter forward. I have now completed line 10, so line 11 is the next line
to execute. Now I have a return statement. What do I do when I see a
return statement? All a return statement is is it's a special type of
assignment statement. It makes a very special variable, whose name is
"return". So in this case, what we're going to do is we're going to add a
third variable to our box; we're going to call it "return"; and inside of
"return" we're going to take the value from x, which is 5, and we're going
to put that inside of the box for "return". The bigger question is, what do
we do with the instruction counter; right? The instruction counter is
always supposed to be the next line, but there is no next line in this file.
Well, OK, so you do exactly that. What we do is we now leave the
instruction counter blank. That tells us that this function is done, and
there's nothing left to do. All right? So now, what do we do when we're
done? Well, what we do is we look at our call frame and we see if there's
a return variable there. It might not; if it's a procedure, it won't have one.
But if there is a return variable there, what we do is we remember that.
And then what we do is we erase the frame entirely. So everything is
gone, but what we remembered from the return is now used as the value.
So in this case, "plus(4)" would be equal to whatever was inside of the

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
return statement for my call frame, which would be the value 5. And now,
since I've used this function call as part of an assignment statement, that
means that now 5 is inside of the global variable y. But the key thing to
keep in mind is, notice that all of my work got lost. We've talked about
before how the return statement is much like working on a math exam,
and then after you've done all your work, you use "return" to circle your
final answer. It's actually worse than that. Everything that is not a return
statement is actually scratch paper. And when the function is done, we
essentially ball up the scratch paper and throw it away. The return value
is the only thing that is kept.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Read: Procedures vs. Fruitful Functions

For a procedure, the function call is a statement.

For a fruitful function, the function call is an expression.

A fruitful function is a function that produces a value.

While most functions are expressions, some are statements. We call


these procedures. A function that is not a procedure is called a fruitful
function because it produces a value. Let's take a closer look at the
difference between a procedure and a fruitful function.

Procedure

The function call is a statement. Example: print('Hello')

Anatomy of a Procedure Definition:

Anatomy of the Body:

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Anatomy of the Header:

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Fruitful Function

Fruitful functions are functions that produce a value.

The function call is an expression. Example: round(2.64)

Fruitful function definitions are almost exactly the same as procedures.


The only difference is a minor change to the body. Fruitful functions have
a new type of statement: the return statement.

Example: temperature converter function

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Visualizing Frames in the Python Tutor
The eCornell Python Tutor is a tool we've created to assist you in building
your visualization skills.

In this video, Professor White uses the Python Tutor to show exactly
what Python does when a function is being executed.

After you have watched the video, you can access the Python Tutor here
or from the next page.

Video Transcript

In a previous video, we showed you how to visualize a function call in


your head. Now, this is a very powerful technique, but like any
visualization technique, it can be hard to tell whether or not you're
actually doing it properly. That's why we have provided a very powerful
teaching tool called the Python Tutor. The Python Tutor is a website
that's linked to from the Canvas page. On the screen, we're looking at a
browser here that has the Python Tutor open, and you'll notice that we
have a field that we can essentially type any Python code that we want
to. I've created a function definition. I have an assignment of a variable x,
I have an assignment of a variable y, and I have a print statement at the
end. I can now execute this. When I execute this code, it's going to run it
just like a script. Everything is going to happen invisibly, except for the
print statements. I hit x, and you'll see right away that it prints out the
value 5, which is what the final answer is. But that's not what we want to
use the Python Tutor for; we actually want to use it to visualize what's
happening invisibly underneath the hood. Well, we can do that by hitting
the button "Visualize."

OK; so now what we've done is we've started to execute the Python code
but we haven't completed it. In fact, you'll notice that I have this red arrow
right here. This red arrow works a lot like the instruction counter in a call
frame. We don't have a call frame yet; we haven't called the function. But
this red arrow shows that, oh, line 1 is the next line that I want to execute.
I haven't finished executing line 1 yet, but it is the next one to execute. To

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
move forward in the code, you just hit the button "Forward." So, what
happened there was, Python read the definition for plus(); it did that
invisibly. That's because while there is a way to visualize function
definitions, that's a bit beyond the scope of this course. So, the Python
Tutor does that part invisibly. But the next part is where it gets a little
interesting. I haven't assignment statement for a global variable x. I
haven't processed it yet; the red arrow says it's the next line to execute.

So I hit forward, and now you'll notice that that's indeed what happened,
is the Python Tutor created a variable x and it put the number 2 in it; it
tells me right away that this is a global variable. Now, the next line is an
assignment statement, but inside this assignment statement, I have a
function call for plus(). So as soon as I hit "Forward" for plus(), it's going
to create a call frame. And we do this, and that's what it's done; it's
created the call frame right down here for plus(), in much the way that we
showed you to write call frames. The name of the function plus() is in the
top left corner. I had to put some variables inside of the call frame.
Remember at the beginning, the only variables that we put inside of the
call frame are parameters. But I do have a parameter n, so I make the
variable for n, and the argument, which in this case is 4, gets copied into
the box for n. The only thing that's missing from the call frame is the
instruction counter. Notice that I don't have an instruction counter here in
the right corner. That's because I don't really need it. That's what this red
arrow is doing right here. The red arrow is telling me that the next line to
execute is line 3. So if we were to draw this as our own picture, we would
write a number 3 up here in the top right corner.

Now what we're going to do is we're just going to go step through the
function one at a time. Let's hit "Forward." It's going to process line 3.
Line 3 is an assignment statement; it's going to create a variable called x
and it's going to put the variable 5 inside of that box. And now I'm ready
to do line 4. OK; so now what I'm going to do is I'm going to hit "Forward,"
and a return statement — remember, it just makes a special variable
called "return" and puts the value 5 in it. And now what do I do for my line
counter? Well, notice that the red arrow's really interesting. The red arrow
is not actually pointing to a specific line; it's sort of pointing halfway
between the 4 and the 5. That's indicating that we're at the end of the
function; there's nowhere left to go. When we did this with our call
frames, we would write an empty box in the top right corner. So, what

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
happens when I hit "Forward" one more time? Well, what it does is it's
now going to jump back to the function call, it's going to erase the call
frame, and right away it's going to create the variable y and it's going to
put the "return" result from that call frame into y. Let's step back; let's
watch it again. I have 5 inside of "return"; moving forward, it's going to
take that 5 and it's going to copy it into y. So, this is how we visualize
what's going on. This is a very, very powerful tool. One more thing that
we'd like to point out is, is that you aren't actually limited to a single field
here. You'll notice that I have multiple tabs here. One of the things that I
could do is I could take this particular function call, I could copy it from
this tab, and I can put it into the second tab. And why is this useful?
Because this essentially, simulates the way modules work. I can "import
tab2" as a module. And now what I can do is I can call plus() by writing
the module name in front. And now if we visualize — there we go; that
designs the global variable x. Now you'll notice that this defined the
global variable y and it didn't create a call frame. A call frame is still
actually created by Python. It's just that when you have the function
definition in another tab, the visualizer gets a little confused and it doesn't
know exactly how to show the call frame to you so it doesn't bother. So
you should keep this in mind if you're actually using the multiple tabs.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Explore the Python Tutor

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Visualizing a Function Call
9LVXDOL]LQJD)XQFWLRQ&DOOFRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Accessing Global Variables
Now that you've seen how useful the Python Tutor can be, it's time to
take a deeper look at how variables and functions work. In this video,
Professor White further explores variables through several examples and
also offers some considerations in using the Python Tutor.

Video Transcript

In this video, we're going to use the Python Tutor to give us a much
deeper understanding about how variables and functions work in Python.
On the screen in the Python Tutor, I have a definition of a function called
swap(). You notice that this is a function that actually takes two
arguments because it has two parameters in the function definition, a and
b. And we can read the description right here, and we can also look at
the code, that the purpose of this particular function is to swap the
variables a and b. So that's my function definition, and now what I've
done is I've created two global variables, a and b — a is 1 and b is 2 —
and now what I'm going to do is I'm going to swap them. Except that what
happens is that this function doesn't really do what we think it does, and
we can tell that by visualizing. Let's go step through the visualizer. We're
going to read the function definition, we're going to create a global
variable a because it's not inside of the body of a function definition.

I'm going to create a global variable called b, and now I'm going to call
swap(). What this does is this creates a call frame. This call frame has
parameters a and b. So right away I have two versions of a on the screen
and I have two versions of b on the screen. All right? So I'm going to
execute the next line of code, and that's going to create a variable called
tmp, and it's going to copy from a. Now, I'm going to copy from b into a,
and from tmp into b. This is a procedure; there's no return statement so it
doesn't have anything to return. And now when I'm done I erase the call
frame, and all of the work that I've done is lost. So what you've noticed
here is that I thought, oh, I wanted to write a function that would be able
to swap two variables, but I couldn't do it because these are global
variables, and these right here are all local variables. So right away you
might think, oh, OK, well, there's no way that I can have local variables

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
and global variables work with each other. Except that's not exactly true.

Let's look at another, more interesting function. So here what I've done is
I've created a variable called a, and now I've created a function called
get_a(), and this once again is a very interesting function definition. You'll
notice that it has no parameters inside of the function header. That's
perfectly fine; I can have a function like that. I just need to be sure that
when I call this function, I call it with no arguments. However, it has no
parameters, but right away, I say, "Hey, I want to return a." So what's
going on? Well, let's visualize what's happening. We'll step through.
We're going to create a global variable called a. Now, I'm going to define
the function get_a(), and now I'm going to call get_a(). That creates a call
frame. I put the name inside of here. There are no variables inside of the
call frame because it has no parameters. And now I'm going to return,
and it says "return a." Well, but how did it know what a was? Well, it knew
because there is a global variable called a. So in this case, it was actually
fine. I was able to use the global variable instead of a local variable inside
of my function definition. So why does this work and why did swap() not
work? Well, to answer that question, let's go a little deeper. Let's look at
another function definition.

Once again, I've created a global variable called a. And now I have a
function definition which I call mask_a(). And right away, what you'll
notice is that assigns a value to a, and it returns it. Step through the
visualization. I'm going to create a global variable called a. I'm going to
call the function; again, I have no parameters, but now I execute an
assignment statement, and that assignment statement now created a
new local variable called a. So now when I return that value, the value
that I'm going to return is the local variable a, which in this case was 3.5
and not 4. So right away we can see sort of this rule of thumb, which is if
we don't assign anything to a variable and that variable exists as a global
variable, we'll use the global version. But if I do assign something to it, it
becomes a local version, and I can only use the local version instead. So
what this means is that I can have global variables but only if I don't
change them. Now you might say, "Well, why is this useful? Why might I
want to have a variable that I can't change?" But we've seen examples
like this before, right? Things — constants like pi or e that are inside of
the "math" module; things that I want to refer to but I don't actually want
to change. With that said, it is possible to actually change a global

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
variable in a function definition, but to do it you have to add a new type of
statement. So as one last example, we're looking at a function called
change_a(). Once again, I've defined a global variable, and now I have
my function definition. It doesn't take any parameters. And at the very
beginning, you'll notice I have this line that says "global a." I'm telling
Python that I actually want to use the global variable a and I don't want to
make a local variable.

Let's watch what happens when I visualize this code. I'm going to make
the global variable a, I'm going to define the function. Now, I'm going to
call it; that creates a call frame that has no parameters. And, oh,
interestingly enough, you'll notice that as soon as I created the call frame,
it actually is not at line 7; it's skipped all the way to line 8. That's because
this is not aligned to execute. Python actually used this line when it was
reading the function definition. But now that it knows that it's going to be
a global variable, it goes, "Oh, OK, line 8 is actually the first line of code
to execute." That, by the way, is the reason why global needs to be at the
very beginning of the function definition; like the specification, it's not
really part of the body. OK; well, now we're going to do this line, and right
away we can see that there's no variable a in the call frame. Instead,
what happened was that assignment statement actually changed the
version of a inside of the global space. and now when I return a, it's going
to return the version that was the global variable. So as you can see, we
can actually modify global variables if we want to. With that said, you
should use this sparingly, right? Programming with globals can get very
confusing, particularly when multiple function definitions are involved, and
it can be very easy to get lost. This is why this technique is a little bit
frowned on by professional programmers. But sometimes you have no
other option, so that's why you might want to use the global keyword.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Access Global Variables
In the Codio project below, navigate to the page Overview of Exercise
3. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 3, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Calling Functions from Functions
A notable feature of functions is their ability to call other functions.

In this first video, Professor White walks through a visualization of what


happens when one function calls another.

Video Transcript

One of the powerful things about functions is that inside of their bodies,
they can actually call other functions. We've seen examples of this
before, right; we use the function print() inside of our greet() function.
Now you might ask yourself, "Wait; does print() have a call frame?"
Actually, it does. The problem is that we can't visualize it because the
definition is hidden. We only know how to visualize a call frame if we can
see the definition. We saw a similar thing in the Python Tutor where if the
definition was hidden in another tab, we couldn't visualize it. However,
suppose I actually have a case where I can see all of the definitions. So
in fact, in the file that we have on the screen right here, you'll notice that I
have two definitions, foo() and bar(), and in the body of the function foo(),
I actually have a call to bar(). So, what happens in this case? At the
bottom of the file, I do have a call to foo() and we know what that does.
But what's going to happen to the call frame for bar() when we execute
the body for foo()?

Well, to understand this, let's step through this one line at a time, OK? So
we're going to look at the function call for foo() right here, and what this is
going to do is this is going to create a call frame. So let's draw this. We're
going to create a box. In the top left corner, we're going to write the word
"foo", which is for our function. foo() has a single parameter called x, so
we're going to make a variable box for x. And inside of that we're going to
put 2, which is our argument. And then, of course, we're going to have to
put the line counter for the next line to execute, which in this case is line
12.

All right. Now what we're going to do is we're going to step through one
line at a time. We go to the next line. We're going to create a variable for

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
y. Inside of y, we're going to take 2 plus 1, or 3, and put it inside of the
box. And then the next line that we're going to execute is line 13. So right
now, we're going to have a 13 in the top right corner of our instruction
counter. OK; but now I have a function call. So, what's going to happen?
Well, what's going to happen is, we're going to make another call frame.
So, right below the call frame for foo(), we're going to create a call frame
for bar(). And what we're going to do is we're going to completely freeze
the call frame for foo(). We're not going to change any variables. We're
not going to change any instruction counters. Right now the instruction
counter is going to be frozen at line 13, and that's because I cannot finish
computing line 13 until I have completely finished with bar(). And now
what we're going to do is we're going to work with bar(), OK? So I'm
going to create a call frame. I have "bar" in the top left corner. bar() has a
single parameter called x; I'm going to make its own version of an x
variable. Inside of that x variable, I'm going to put the argument that was
put there, which was y that I evaluated to 3. And then for the instruction
counter, I'm going to give it the next line of code to execute, which in this
case is line 17. So I have two call frames; one at line 13 and one at line
17.

Once again, we're going to go through lines one at a time. We're going to
process line 17. We're going to create a variable called y. We're going to
take 3 minus 1, or 2, and put that into the variable y. And now we're
going to change our instruction counter to line 18. Now we're going to
take line 18, which is a return statement. We're going to make a very
special variable called RETURN. We're going to take the contents of y,
which is 2, and put that into RETURN. So RETURN now has 2 and our
instruction counter for bar() is blank because we have reached the end of
our function definition. Finally, what we're going to do now is, now we're
going to erase the call frame for bar(), and that's going to allow us to
make progress on line 13. So, we make a variable for z inside of the call
frame for foo(). We put the number 2 inside of z. And now we can finally
increment our instruction counter to point to line 14. And now we just
keep on moving forward. We create ourselves a return variable, and we
put the number 2 in it. Our instruction counter is now blank. Finally, we
erase that, and now we create a global variable called w, which has our
final answer inside of it.

In this video, Professor White shows how visualization of the same code

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
differs when the Python Tutor is used.

Video Transcript

We've shown you how to visualize what happens when one function calls
another. But in that case, what we did was we showed you how to
visualize it in your head. In this video, we're going to show you how to do
this in the Python Tutor; and in doing so, we're going to show you why it
was that we didn't show you the Python Tutor to begin with. Here I have
the Python Tutor open, and I have the same code that we had from a
previous video. I have my functions foo() and bar(), and my function foo()
calls bar(). And at the very bottom, I have a call to foo(). This is pretty
much the same as what we had before, though we should note that the
line numbers are slightly different. Instead of foo() starting at line 11, it
now starts at line 1. So everything is off by 10. But otherwise, everything
is pretty much the same. So, let's step through and visualize this code.
We hit "Forward" and we're going to read the function definitions.
Remember that function definitions are processed silently. So now we're
ready to call the function foo(). We hit "Forward" and we're going to
create a call frame for foo(). Here we have the call frame. The name of
the call frame is the top left corner, and we've created a variable x, and
that's because I only have one parameter, which is the variable x. I don't
have a line number, but that's what the red arrow is doing right here; it's
telling me that the next line to execute is line 2.

So let's hit "Forward" to execute line 2. That's going to assign to a


variable y, and now we're ready to execute line 3. However, line 3 has a
call to bar() in it, so when I hit "Forward", it's now going to create a call
frame for bar(). And remember that when it does that, it actually froze the
call frame for foo(). And we can tell that it is frozen because it's no longer
colored blue; only the active call frame is actually colored blue at any
given time. So here's my call frame for bar(), which is active. It also has a
variable x, which is my parameter. And my instruction counter is currently
at line 7 because that is the next line to execute. So let's go step through.

I have now processed line 7, and that has created the variable y. And
now we start to see why it was that we didn't quite show you the Python
Tutor to begin with. Because look; we see this gray arrow right here, and
we thought maybe the gray arrow was keeping track of what was

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
happening in foo(), but actually it's just the previous line that I've
executed. I don't actually have an arrow pointing to anything in the
definition for foo() anymore. That means if I wasn't paying attention, I
may not remember where it was that I was supposed to go back to when
I was done. When we were visualizing things, we had solved that
problem; we would have written the number 3 up here in the top right
corner before we froze it, telling us where to go back to. But in the Python
Tutor, we're just going to have to remember those things. With that said,
let's keep moving forward. Now we make a return value; we're going to
put the number 2 in it, and we're done. As you can see, the red arrow
isn't actually pointing to any specific number, telling us it's ready to end
this particular function call. So now I'm going to erase it; and when I
erase it, it's going to take the return value and it's going to copy it into the
variable z, right here. And now you'll notice that foo() is no longer frozen
because it's blue, and the instruction counter has moved ahead to line 4.
Let's execute line 4; that creates a special return value which has 2 in it.
And then we go forward one last time; it's going to erase this frame and
it's going to take the return value and it's going to put it into a global
variable, which is

In this video, Professor White demonstrates a situation where a group of


functions call each other, and how this creates multiple call frames at one
time. The collection of all of the active frames is known as the call stack.

Video Transcript

We've seen how to visualize two functions, where one function calls
another. But we don't have to stop there, right? We could have lots of
functions all calling each other. To see what happens there, let's look at a
specific example. On the screen here I have four function definitions —
function1(), function2(), function3(), and function4() — and at the end I
have a call to function1(). You'll notice that function1() calls function2(),
and function2() calls functions 3() and 4(). Let's visualize this and let's
see what happens. At the beginning, stepping through our visualization,
we process each of the definitions invisibly, and now we've finally
reached the call for function1(). Going forward, it's going to create a call
frame for function1(), and you can see that the next line is line 2, which
itself has a call for function2(). When we hit "Forward", that's going to
execute that call. It's going to freeze function1(); it's no longer blue. And
© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
now, I have a call frame for function2(). I'm ready to execute line 6, which
itself has a function call in it. Again hitting "Forward", now I've frozen
function2() and function1(), and I have a call frame for function3(). And
you can see how this process goes. This is what we call the call stack.
The idea is that we're working with an analogy like a stack of plates,
right? Every time that you call a new function, you stick a new plate on
top of the stack, and you can't access the previous plates in the stack
until you get rid of the plate that's on top. Now, it might seem a little
strange here because you notice that we're sort of working top-down,
right; the plate at the bottom of the stack is actually the top on the screen,
and the plate that is the top of the stack is down at the bottom. But if you
put yourself upside-down on your head, it'll all work out. OK; so let's just
continue with the call stack. This function is now going to return and it's
going to be erased. And now I'm ready to do function2(). But function2()
is still not ready to be finished, because what I'm going to do is I'm going
to stack one more plate on top of it as I call function4(). Now function4()
is finished. Now that both functions 3() and 4() are finished, I can finally
finish function2(). And now that I can finally finish function2(), I can finish
function1(). Already we can see that this the real power of the Python
Tutor. When we have lots of functions, which is typically what happens
when we have very, very complex Python code, we can put it into the
Python Tutor and see exactly what's happening with all of these call
frames. Because understanding call frames is really important when
you're understanding things like memory, right? Since all of the call
frames have to be in place and they can't be erased until you're finished,
if you keep making new call frames, you're just using more and more
RAM on your computer. And this can be really important for advanced
programs.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Break Up Functions
In the Codio project below, navigate to the page Overview of Exercise
4. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 4, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Visualizing the Call Stack
9LVXDOL]LQJWKH&DOO6WDFNFRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Defining Optional Arguments
The next activity in this module will ask you to add optional arguments to
a function. But what exactly are optional arguments?

In this video, Professor White demonstrates how to make certain


arguments optional and the rules that must be followed when doing so.

Video Transcript

When you've worked with Python, you might have realized that some
functions actually take a variable number of arguments. To see what we
mean by that, let's talk about the function round(). So the round() takes a
float as an argument, and in this case I'm going to give it 26.54. And
when we hit the Return here, we'll discover that it evaluates it to the
integer that's rounded up from this; it takes the 5 and it rounds it up to 27.
That's OK if I give it one argument, but I can actually give two arguments
to round(), separated by a comma. So I'm going to give it that float and
I'm going to say ",1". Now what you'll notice is that I get a very different
answer, and what's happening is that Python is no longer rounding it to
the nearest integer; it's actually rounding it to the nearest ones place after
the decimal. So I get one set of behavior when I call it with one argument,
and another set of behaviors when I call it with two arguments. So this is
what we mean by a function that takes a variable number of arguments.
In this video, we're going to see how to do this with our own functions.
We're going to be working with a module called "opt". Inside of opt is
going to be a function called "point string". And what this does is this just
takes three values, which are essentially x, y, and z coordinates. So I'm
going to have x is 1, y is 2, z is 3, and it returns a string representation of
that three-dimensional point. So, that's what we've done here; we have
the point at 1, 2, and 3. The interesting thing about this function is I don't
actually have to call it with any arguments at all. I can actually just call it
with a set of empty parentheses, but it's not going to crash. What
happens in this case is that Python realizes that, oh, I must be talking
about the point at the origin, when x is 0, y is 0, and z is 0. So, how did I
get this to happen?

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
If we look at the definition of point_str here, we'll notice that it's very
similar to the definitions that we've seen before. We have a header, we
have a specification, and we have a body. The big thing that's different is
the parameters in the function headers. Normally, we would just list x, y,
z here. But you'll notice that we have some assignment statements. What
these assignment statements are doing is they are assigning default
values to each of these parameters. And we're telling Python, "OK; if
something is missing here, that's the value we should use." So since we
didn't put anything inside of the parentheses, it just used all zeros. Well,
we can actually build up on top of that. We don't have to do all or nothing.
We could just call it with a single value. I'm going to call it with — actually
let's call it with 4 so it looks a little different here. And now what you'll
notice is that since it saw 4, it's going to put a 4 in the very first
parameter, which is x; and for everything else, which is y and z, it's going
to use the default value. Similarly if I do 4, 5, now it has values for x and
y, but it doesn't have a value for z, so it's going to use the default. Now
you might say, "OK; well, what if I want to assign to y but not to x and z?
Because whenever I write a number by itself, it's going to go put it in the
first parameter that it can find." Well, so now we do a slight different
change in how we call the function. We're going to write "y=4".

So now, normally we would have an expression inside of the function


call. Now we've actually written an assignment statement inside of the
function call, and we're telling it to assign the value of 4 to the parameter
y. And when we hit Return, that's exactly what happened; x and z got the
default values of 0, and y got the value of 4. We can mix and match this
idea. We could say, "OK; I'm going to specify 1, and that's going to go
into x. But now I want it to skip over y and assign a value of 4 to z."
However, the important thing to keep in mind is that when you mix and
match, you always have to put the assignments last. So I can't do
something like "x=2,3". You might think, "OK; this'll assign 2 to x and 3 to
y." But actually this is an error, and what it's going to tell you is I have a
positional argument; that's 3. It's something that Python's going to figure
out what parameter to choose from the position that follows a keyword
argument; that's what we mean by "I have an assignment statement for
x," and that can't ever happen. We see a similar thing in the function
definition as well. So in our function definition, we have default values for
all of our parameters. We didn't have to do that. Let's remove our default
value for x here. Let's save. And now we're going to have to quit Python

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
so that we can reload it. Let's "import opt". And now, if I call point_str()
with no arguments, you'll notice that we get an error. And that's because
x doesn't actually have a default value; I have to give a value for x. But if I
give a value for x, it's now happy, and I can also essentially either give
values or use default values for y and z. But the last thing to keep in mind
is when we do this mix and match, once you assign a default value, like
we've done here for y, all of the remaining parameters must have default
values. So let's suppose we did something slightly advanced, where we
said, "OK, x and z are not going to have default values, but y will." We're
going to quit Python, reload this module, and now you notice that it
doesn't work; we've run into an error. And that's because Python can't
even read this definition at all. And that's because, as we said, once you
define one parameter with a default, all of the parameters from that point
on must have defaults as well.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Create Optional Arguments
In the Codio project below, navigate to the page Overview of Exercise
5. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 5, you
are done. However, do not submit the Codio project yet.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Tool: Fundamental Python Concepts 1

Click here to download the


Fundamental Python Concepts
Tool. Pages 1 and 2 cover all the
content we have seen in this
module.

So far, we've looked at different types of functions in Python and how to


execute them. We've also examined procedures, fruitful functions, and
local and global variables. This reference tool contains key Python
concepts we've covered in this module. Check out the first two pages to
help you review things such as basic terminology and the difference
between a procedure and fruitful function.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Project Submission for Executing Functions
You have now completed all Codio exercises for the course module
Executing Functions. To submit the project, go to the page Complete
the Course Module in the Codio project and follow the instructions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Wrap-up: Executing Functions
In this module, you saw how to define your own functions and exactly
what happens when you execute these definitions. Professor White
reviewed basic terminology and explained how to define a procedure.
You also explored the differences between a procedure and a fruitful
function and practiced visualizing function calls using the Python Tutor.
Lastly, you examined how to access global variables, call functions from
functions, and define optional arguments.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module 2: Specifying Functions
Module Introduction: Specifying Functions
Watch: Motivating Function Specifications
Motivating Function Specifications
Watch: Writing Specifications
Reading Specifications
Watch: Specifying Preconditions
Watch: Identifying Types of Preconditions
Identifying Preconditions
Watch: Recognizing Bad Specifications
Tool: Fundamental Python Concepts 2
Identifying Bad Specifications
Module Wrap-up: Specifying Functions

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Introduction: Specifying Functions
In this module, we'll take a deep look at function
specifications. Professor White will explain the key role
that specifications play in the field of software
development. You will explore how to write specifications
and specify preconditions. In addition, you'll practice
identifying types of preconditions and recognizing bad
specifications.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Motivating Function Specifications
You may be wondering why this course has an entire module on
something as straightforward as specifications.

In this first video, Professor White explains the key role that specifications
play in the field of software development. Software development is a
business, and it's not just about writing code but also about proper
business processes.

Video Transcript

This module is dedicated to the concept of a function specification. Now,


to remind ourselves what a function specification is, it's just the docstring;
it's the beginning of a function definition. Here on the screen, we have the
definition for a function called greet(), and right at the very beginning of
the body, you'll notice that I have a lot of text that's in between two sets of
triple quotes. This is the specification. An important thing about the
specification is that this is what happens when I get the Help menu. If I
import this particular module, and now I ask for help on the function
greet() inside of this module, the information that I get is the docstring.
Specifications also appear in web pages. If we were to pull up the web
page for the IntroCS module, which contains a lot of the functions for
working with strings, we'll notice that this documentation has a lot of the
same format as the specifications that we saw inside of our Python
module. And this is really important because you need these types of
specifications and documentation for knowing how to use a function. I
mean, what if you want to use a function but you weren't the one that
wrote the definition? And a lot of functions, such as those that are built
into Python, you can't even see the definition at all. So that makes sense
why it's useful to have these types of things, but you might ask, why do
we need to have an entire course module about specifications? Why
don't we just say, "Hey, you should write good comments and ways that
people know how to use your code." Well, the problem is it's a little more
complicated than that. Because what makes a specification good?
Software development is a business. It's not just about writing code; it's
also about proper business processes, these processes that can enable

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
better code development. One of the things to understand is that complex
projects need multi-person teams. Sure, you might have some projects
that involve a lone programmer for doing very simple contract work, but
anything else you typically need to have teams of people working
separately but bringing back the work that they do together. So a lot of
these processes are about how do we break up this work? What pieces
of the code do we give to each team member? And how do we fit these
pieces back together? Now, you might have heard of some of the names
of some of these processes; you might have heard of things like waterfall,
or iterative, or agile, or maybe even DevOps. All of these terms are
actually beyond the scope of this course. You need to have a stronger
programming background to understand what these terms mean. But
there is a basic principle underlying all of these methodologies; this
principle about enabling the communication between your programmers
and how to integrate your work. And that principle is the specification.
And that is why that is the focus of this course module.

In this video, Professor White explains how functions are a way to break
up software development among team members and how specifications
enable that separation of responsibilities.

Video Transcript

The reason why specifications are so important for managing teams of


programmers is because functions are a way that we separate up work. If
we have a function, we can actually have multiple actors involved in it,
right? Always we'll have one developer who's the person who defined the
function, wrote the body of the code, but I might have another developer
who just calls this function and uses this function within their own code.
This gives us a very natural way to break up very complex pieces of
software. So, suppose I have some big project that I want to work on.
Well, your manager, who is generally some sort of technical role, like
maybe a principal software engineer, is going to take this complex project
and break it up into several small functions. That manager is then going
to distribute those functions about various developers on their team. So
one developer might have one set of functions, another developer might
have another set of functions. Each of these developers are responsible
for defining the functions, but they're able to call the functions produced
by another developer. So function calls are a way that these developers
© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
can communicate with one another.

Now, an interesting thing that happens here is, what happens when a
code breaks, right? Suppose I have a bug in my code and I've narrowed
it down to a single function. But there are multiple people involved, right;
there's the person who defined the function and the person who called
the function. So, whose fault is it? Who wrote the code that had a
problem, and what do we have to do in order to fix this? Well, this is
exactly the purpose of a specification. It clearly lays out responsibility.
What does the function promise to do and what are the allowable uses of
that function? So from that responsibility, we can determine, is the definer
at fault or did the definer implement the function properly? On the other
hand, maybe the caller is at fault because the caller used the function in
a way that wasn't allowed. So, a specification isn't just helpful comments;
it's actually a business contract. And because it's a business contract, it
requires a much more formal documentation style. And in fact,
techniques like agile or iterative, what they are is they're ways to safely
modify a contract after it's written, but they don't necessarily have
anything to say about the contract itself. Now, you might ask, why do you
need to know all of this? Well, we've taught you how to write functions,
and you know all of the technical details that you need to know about
that, but that's not what people get paid money for. People get paid
money to write code to solve problems. You're given some sort of
specification of a problem in English, and you need to write code to that
specification. Doing that properly and well means that you need to
understand specifications. What makes a good specification, and what do
we do if we have a bad specification?

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Motivating Function Specifications
0RWLYDWLQJ)XQFWLRQ6SHFLILFDWLRQVFRGLQJH[HUFLVH


Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Writing Specifications
Now that you've seen the importance of specifications, the next step is
learning how to write specifications effectively. In this video, Professor
White breaks down the structure of a specification to show how, when
done correctly, it should clearly communicate what the function does.

Video Transcript

Now that we know why specifications are important, let's look at some
example specifications and let's see how they're structured. On the
screen, we have a definition for a procedure called greet(), which prints
out some information for the person who's calling it, which we'll call the
user. Right away, we can see that the specification's pretty long; it's
actually a lot longer than the rest of the body itself. There's a lot of stuff
going on here. The first thing that we'll notice in the specification is a
description of what the function does. It's supposed to print out a greeting
to the person. Interestingly enough, you'll notice that we have a blank
line, and then now we have another description, which seems to say a lot
of the same things that the first one did. And there's a reason for this. In
every single Python specification, what we like to have is a single one-
line summary of what the function does. The purpose of this is, I might be
a user who has several functions to choose from and I don't know which
function is right for me. Instead of having to read all of the specification in
detail, I can just very quickly look at these one-line summaries. But that's
exactly what they are; they're summaries, and they might not have all of
the details. In this case, in this summary, I know that, oh, it's going to
print out a greeting to the name n, but I don't know what it really means to
be a greeting. Well, after that I have some more detail where I tell the
user, "Oh, the greeting is actually something of the form 'Hello' plus
name. Oh, and then I might have something after that to try to start a
conversation.

Now, these extra details don't have to be a single sentence; they can
actually be multiple paragraphs. It's not uncommon for the function
specification to be a lot longer than the rest of the body of the function
itself. After you're done with all of these details, the last thing that we do

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
is talk about the parameters, right? Remember that parameters are the
variables that exist in the header of the function definition. To call a
function, I need to provide arguments which are evaluated and plugged
inside of these variables. That means that the person who is calling that
function needs to know, what are the right types of arguments to provide
this function? Right away I'm telling them that, "Oh, parameter n is
supposed to be the name that you use in the greeting." So I know that
when I call the function greet(), I'm supposed to give it a name as an
argument.

After this parameter you'll notice an interesting word called


"Precondition." This is an important topic but this actually needs its own
video. So we're going to ignore this for right now. Instead what we're
going to do is we're going to look at a different type of function.
Remember, this is a procedure; there is no return statement here. So, if
we were to look at another function definition — in this case,
to_centigrade — this is an example of a fruitful function, which has a
return statement in it. But it doesn't seem like there's a lot of difference in
the specification. You notice that we have a one-line summary of what's
going on. We have a little more fine print here telling the user that, "Oh,
this function is going to return a float and not an integer." And then we
have a description of the parameter, telling the user that the value that
they provide to this function should be a temperature value in Fahrenheit.
There is one important difference in this function specification, and that is
right away you'll notice that the one-line summary starts with the word
"Returns." That's driving home very quickly that this is not a procedure
but a fruitful function. Because if I'm looking at a bunch of functions and
trying to decide is this function right for me or not, I do want to very
quickly be able to separate the procedures from the fruitful functions.
Now, a lot of the things that we've just described for the specifications are
actually sort of standard Python procedure. There is a guidance for how
to write specifications that's called PEP 257, PEP stands for Python
Enhancement Proposals. This is where the people who work on Python
sort of set out sort of best practices. Now, we've linked to this particular
web page from the Canvas page, and you can read this if you want, but
actually it gives you not a lot of details. It gives some sort of guidance,
like namely you should have the one-line summary, but for the most part
it gives a little too much flexibility for a beginner. And one of the things
that you're going to discover right away is that writing specifications is a

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
lot harder than coding, and learning how to specify is actually a larger
part of a computer science degree than actually writing code. That's the
reason why our specifications are a lot more structure than what you're
going to see in this guidance. And the purpose of this is to hopefully cut
down on your mistakes right now. But with that said, we do adhere to the
guidance from the PEP guidelines.

As Professor White discussed in the video, the Python website provides


some general recommendations for specification writing on the PEP 257
page.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Reading Specifications
5HDGLQJ6SHFLILFDWLRQVFRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Specifying Preconditions
Specifications are a form of business contract. Preconditions are a way of
making that contract formal. They are a promise that, when a function is
called with all arguments satisfying the preconditions, then the function
will work correctly.

In this video, Professor White further explains how preconditions work


and the way in which they help assign responsibility within software code.

Video Transcript

In a previous video, we looked at the anatomy of a function specification.


But we left out one important detail, and that detail was the concept of a
precondition. If we return to the definition of the function to_centigrade(),
we'll notice that we have this thing called "Precondition" at the very
bottom of the specification. It's right below the description of the
parameter x. And interestingly enough, this precondition really just gives
us more details about that particular parameter. That's what
preconditions do; they say things about the parameters. If I were to have
a function with multiple parameters in it, each parameter would have its
own precondition. Of course, you might look at this and go, "Well, if it's
just telling me something about the parameter, why didn't I just put it in
the description of the parameter in the previous line?" Well, remember,
specifications are supposed to be a form of business contract.
Preconditions are a way of making that contract formal; they are a formal
promise. The promise that they're making is, "If, when the function is
called, the precondition is true, we promise that the function will work,
and it will work correctly." On the other hand, if the precondition is false,
all bets are off. Maybe it works; maybe it doesn't. Let's see this in
practice.

What I'm going to do is I'm going to import the module "temp" and I'm
going to call the function to_centigrade() on 32 degrees Fahrenheit. I hit
Return and I get 0 degrees centigrade back. That's what I expected. And
that's because I called this function properly. The argument that we
provided was a float, just as was requested by the precondition. We know

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
it's a float because of the decimal point right there. Now I'm going to do
something a little cutesy. I'm going to call to_centigrade() on 32, but
instead of doing 32 as a number, I'm going to do it as a string. Right
away, you'll notice that it crashed. Clearly it crashed, right?
to_centigrade() isn't designed to work with strings, right? I say that I want
it to be a float, but I provide it with a string instead. This is what we call a
precondition violation. We've called the function, but the argument that
we provided violates the precondition that's in the specification. Now, one
of the things you have to be careful with is, not all precondition violations
result in a crash. Sometimes, it might still work. Let's do one more
example. This one's a little less clear-cut. Again I'm going to call
to_centigrade() with 32; this time as a number, not a string. And of
course, it seems to work, but this is technically a precondition violation.
That's because 32 here is not a float; it's an int. There's no decimal point
here. So, that does indeed violate the specification, though it still worked.
This is what we call undocumented behavior. The definer didn't promise
that it worked with integers, but it still did. Taking advantage of
undocumented behavior is very bad practice, right? Remember that the
specification acts as a form of contract. The definer of the function is held
to that contract, but they can change anything else at any time. So the
definer for this function later on could decide, "Well, I didn't promise for it
to work for integers," and so they might make it crash instead. This hits
Microsoft developers a lot. Microsoft is very, very good about keeping its
specifications fixed, but they change details that are unspecified all the
time. If you take advantage of unspecified behavior and your code
doesn't work, you have no one to blame but yourself. So indeed, that's
what we're talking about: the notion of responsibility. Suppose I have a
function; the people involved with the function are the person who defines
the function, and then I have someone who might call the function. And I
get an error, and I've somehow localized that the error is somewhere
around this function. And I ask, which of the two parties here are at fault?
Well, right away we look at, was the precondition violated? If the
precondition was violated, we absolutely know that it is the fault of the
person who called the function, and they are responsible for cleaning it
up and fixing their code. On the other hand, if the precondition was
properly met and something didn't go right, then it's the fault of the
developer, the person who defined the function, and they are the one
who needs to go in and fix that code.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Identifying Types of Preconditions
Now that you've seen what preconditions do, it's time to examine them
further.

In this video, Professor White describes how preconditions are divided


into two groups, type restrictions and general restrictions.

Video Transcript

We've seen now that a precondition is arguably one of the most


important parts of a specification. Now, what I want to do is I want to back
up and I want to show how preconditions really break up into two kinds.
The first kind of precondition is what is known as the type restriction. If
we look at the definition of to_centigrade(), you'll notice that the
precondition, all it says is that x has to be a float, right? I'm saying
something about the type of the argument; I guarantee that it always
works if it's a float but not necessarily if it's a string or if it's an int. Not all
preconditions work that way. Let's look at a slightly more complicated
precondition. Here I have a function called get_number(). And what
get_number() is supposed to do is it's supposed to take a file and read
that file and give me a number back from that file. In this case, the file
that I'm going to look at is the file called number.txt. And you'll notice
inside of this file, I have a 4. So, what we're going to do is we're going to
import the module "read", and we're going to call read.get_number(). And
we're going to give it the name of the file, which in this case is
number.txt. It gives me the number 4. All right; now let's call this function
again, but let's say 'number2.txt'. Well, there's an error, and that's
because nowhere in this directory do I have a file called number2.txt.
However, both of these were strings, right? This was a string which had
the name of the file, and this was also a string that had the name of the
file. So it wasn't actually a problem with the type here. In fact, if you look
at the precondition, you'll see right away I say that filename has to be a
string, so that's a type restriction.

But there's actually a more complicated thing here; I say that it has to be
a reference to a valid file. In addition, I say that the file only has a single

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
integer in it. So if I were to make a change, and instead of having a 4
here, I would replace it with an a. Now, if I were to read from this file, now
it would crash because it can't turn an a into a 4. So all of these things
that we see inside of the precondition are important things to tell me
about whether or not it's proper to call this function or not. Now, why do
we separate these two; why do we separate type restrictions from more
general types of restrictions? That's because it's actually easy to check
whether or not a type restriction is violated or not. Let's clear Python, and
let's make a variable called x. Well, there's actually a function in Python
called type(), which allows us to check on the type that is stored inside of
that variable right now. I can see right now that what's inside of that is an
int. If I were to change the value of x to 3.5, now when I look at the type
of x, I can see that it's a float. And I can actually write code to check
whether or not it's an int or a float. If I were to write this as a Boolean
expression, "== int", that's false because it's not currently an int. If I were
to write "== float", that's true because it is equal to a float. And this is nice
because having ways to actually check whether or not a precondition is
violated or not can often make it easy to find whether or not there are
problems in our code or not. Other languages, like C or Java, actually go
even further than this; they actually restrict the way that you can use
types in their languages. Here, in Python, I was able to take the variable
x and I could put an int in it at one time and a float at another time. In
those other languages, the variables themselves have types. So int
values can only go inside of int boxes, and float values can only go inside
of float boxes. These are what are known as statically typed languages,
and it acts as a way to enforce preconditions. People typically find this
ideal for very large and complex software. On the other hand, languages
like Python and JavaScript, well, they're dynamically typed. They'll allow
anything, but they'll crash if they're misused. That's the reason why those
languages have to rely entirely on the specification. So an interesting
question is, is statically typed better or not? Well, some people prefer
static typing for beginners because it'll quickly shut down whenever you
violate your preconditions, and a lot of errors are much easier to find. But
this creates a false sense of security. As we pointed out, not all
preconditions are expressible as a type; some are more general, like that
file. So you need to get used to reading specifications anyway, and that is
why we put so much focus on specifications.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Identifying Preconditions
,GHQWLI\LQJ3UHFRQGLWLRQVFRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Recognizing Bad Specifications
Now that we have introduced the idea of a function specification, let's
look at what exactly constitutes a good specification.

In this first video, Professor White shows an example of a specification


and explains what makes it either good or bad.

Video Transcript

Most of the time when you're working with a function you only have the
specification to go by. Typically, the definition of the function is hidden
away somewhere on your computer. Even if you have access to the
definition open in a file, well, a lot of times functions are written by
subject-matter experts, and it might be too complicated for you to
understand that definition. With that said, not all specifications are good.
They can be incomplete, they can be missing details. This is particularly
common in open-source software. But what this means is you need to be
able to figure out whether or not a specification is good just by reading
the specification itself; not by looking at the definition. This is a very
imprecise scale; it's not a science. And it's something you just build up
with experience. So with that said, there are some sort of rule of thumbs
that you can use. Right away, when you look at a specification, the first
thing you should ask yourself is, are the preconditions clear? Can you tell
what is allowed, what is not allowed? You should pay close attention to
whether or not there are any sort of type restrictions on the preconditions.
But with that said, you should also think about the weird cases; things
that aren't precondition violations, but all seem to violate the spirit of the
specification. Beyond the preconditions, you should think about the return
result if it's a fruitful function. For every single argument that satisfies the
precondition, do you know what answer should be returned? If not,
maybe you need some more information in the specification. Procedures
are the same way. They don't return anything, but they have an outcome.
So you ask yourself, is the outcome for this particular argument clear? All
of this is a little abstract, so let's look at a very specific example.

Here we have the start of a definition called number_vowels(). As you

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
can see, we don't actually have a definition here; we're going to
implement this definition later. But we do already have the specification,
and there are a lot of nice details in the specification. The specification
tells us that this is a fruitful function that's going to return the number of
vowels in the string w. The precondition seems to be fairly precise. It tells
us, OK, we only want to have strings. We don't want to count vowels
inside of a number like 24. And in addition, that string should only have
letters, so we don't want to deal with punctuation or any sort of fancy
things in our strings. That looks pretty clear. But let's think about it a little
more carefully, right? So, let's just think about particular examples. Let's
think about the word "hat." Well, this satisfies the precondition; it's a
string, it only has letters in it. So that's good; we understand that. But do
we know, without hitting Return now, what the return result should be?
Well, a is a vowel; it's the only thing here that's a vowel, so the answer
should be 1. Great. That's our answer. Let's do something a little more
complicated. Let's suppose I have two vowels. Let's do the word "heat,"
all right? Well, there's an e, and there's an a. Those are the only vowels;
there are two of them. I expect the answer to be 2. Indeed that's the
case. All right; let's go for some weirder questions. How about "sky"?
Right away, now we start to realize it's unclear what the answer is. Is y a
vowel, right? When we talk about vowels, we say, "and sometimes y." So
is the answer 1? Or is it 0? Well, in this case, from context I might say,
"OK; well, it's a vowel, so the answer is 1." But let's suppose we had the
word "year" instead. Now is the answer 3? Or is it 2? And right away we
start to realize that things are getting complicated. But the reason why
they're getting complicated is because we're actually adding extra
assumptions that are not in the specification itself.

For example, let's suppose I type this word. This is not an English word.
Nowhere in the specification did I say these strings have to be English.
This is a Welsh word. And in this case, there is a vowel; that vowel is w.
So should I include w as well? In fact, it's even more complicated than
that. At no point did we say these had to be words at all. All we said was
they were strings with letters. So we should be able to type anything in
here and it should give us an answer about the number of vowels.
Beyond that question, we also ask ourselves, what does it mean when
we talk about the number of vowels? Let's suppose we have the word
"beet." Is the answer here 2, or is it 1? I mean, there are two letters e, but
there's only one distinct vowel e. So do I allow repeats, or do I not count

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
repeats? If I don't count repeats, do I make a distinction between
lowercase or uppercase? So what we've actually seen here is this is
actually a pretty bad specification. It's very, very incomplete. And what's
happening is it's taking advantage of the confusion between string and
word. And what's happening is we're filling in the details with our own
assumptions. This is very, very dangerous. Most of our assumptions are
cultural, and a lot of times when you're working on a software team,
you're working on an international team. Cultural differences between a
specification can cause a breakdown between teams. In fact, even in my
own classes working with certain students from Asia, they don't
necessarily know right away what a vowel is. This is why we need to be
very, very precise in our reading and make sure that we're only doing
exactly what the specification says, and nothing more.

Bad specifications are specifications that are incomplete or missing


details. In this video, Professor White shows some steps you can take to
fix a bad specification.

Video Transcript

In a previous video, we worked at the definition of this function called


number_vowels(). And it seemed that it had a fairly precise specification,
but we saw that there were several problems with it; namely, we didn't
know how to handle the vowel y, and we also didn't know what it meant
to be number of vowels. So now what we're going to do is we're going to
look at a much better specification. And we can see that this is much
longer, filling in a lot of details. We still have the one-line summary, and
this one-line summary still has a lot of imprecision to it; it just tells us,
"number of vowels in string w." But I'm going to take advantage of the
fact that I have a lot more that I can write in a specification to fill in the
details. Right away, we're going to define vowels to only be vowels in the
English language. So a, e, i o, and u. For y, what we're going to do is,
we're going to just by fiat say that y is a vowel only if it's not at the start of
the word. So that means that for "sky" it's a vowel, but for "year" it's not.
Now, this is not realistic. We can certainly think of cases where y might
be in the middle of an English word but not a vowel. But we're still not
claiming that any of these things have to be English, so we're just going
to make a de facto rule here in the specification. We're also saying that,
oh, repeated vowels need to be counted separately, and both uppercase
© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
and lowercase vowels are counted.

Now, while this detail should be enough for someone to go off of, what
we're also doing here is we're providing several helpful examples to the
user to make these details clear. And one of the things that we're trying
to do in these examples is go through all of the possible arguments
where there might be some ambiguity and resolve that ambiguity. So we
have a simple example with just one vowel, and here we have another
example with multiple vowels, but we've made it clear in this particular
example that repeated vowels are counted as well. In the next two
examples, we say, "Aha, 'sky,' now this counts as a single vowel, but
here where y is at the beginning, it doesn't actually count as a vowel."
Now, so one of the things that happened here, right, is that we took a
specification that was imprecise and we made it much more precise.
However, one of the things you must understand is, typically you're not
allowed to do this; not even if you are the function definer. That's
because specifications typically come from higher up in the management
chain; they're coming from, say, your senior software engineer. And the
rules for changing specifications are very important. A lot of the
methodologies that you might have heard of, like agile or waterfall or
iterative, are really rules about changing specifications. If you're someone
who's implementing a function but you've been provided a specification, if
the specification is unclear, you should always ask for more guidance;
typically from the specification author who's higher up in the chain. In this
course, that means the instructor. So if any project, we give you a
specification that you don't understand, you should ask us those
questions because we would rather that you get it right the first time,
because adding new assumptions is always dangerous.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Tool: Fundamental Python Concepts 2

Click here to download the


Fundamental Python Concepts
Tool, if you haven't already. Page
3 covers all the content we have
seen in this module.

In this module, we looked at function specifications. You explored how to


write specifications and specify preconditions. This reference tool
contains key Python concepts we've covered in this module. Check out
page three to review how to write a good specification and how to identify
types of preconditions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Identifying Bad Specifications
,GHQWLI\LQJ%DG6SHFLILFDWLRQVFRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Wrap-up: Specifying Functions
In this module, you took a deep look at function specifications. Professor
White explained the key role that specifications play in the field of
software development. You explored how to write specifications and
specify preconditions. You also practiced identifying types of
preconditions and recognizing bad specifications.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module 3: Testing Functions
Module Introduction: Testing Functions
Watch: Identifying Errors
Reviewing Terminology
Watch: Designing Test Cases
Designing Test Cases
Designing Tests with Multiple Arguments
Watch: Scripting Tests
Activity: Script Tests
Read: IntroCS Documentation
Watch: Organizing Test Cases
Activity: Organize Test Cases
Watch: Debugging Discovered Errors
Activity: Debug Discovered Errors
Tool: Fundamental Python Concepts 3
Project Submission for Testing Functions
Module Wrap-up: Testing Functions

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Introduction: Testing Functions
In this module, Professor White explains how to check a
function for errors. Then, you will examine how to design
test cases to search for errors. Professor White also
introduces the unit test, a faster way to test, debug, and
retest a function. In addition, you'll explore how to
organize unit tests and how to debug discovered errors.

A tool that you'll encounter in this section is the IntroCS module. If you
haven't already installed this, do so now.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Identifying Errors
A common first step in checking a function for errors is to perform testing.

In this video, Professor White introduces the terminology of testing and


the process you will follow in testing functions.

Video Transcript

In this course module, we're going to talk about how we test the functions
that we write. Now, before we do this, I want to introduce some important
terms; terms you've probably heard before, but I need to make them
more precise so we can be careful about how we use our language.
Throughout this course module, I'm going to talk about bugs, and bugs
are just errors in our programs. I'm not going to be any more precise than
that. And if we're talking about bugs, obviously we talk about debugging.
And debugging is just the process of finding and removing bugs. Testing
is not the same thing as debugging. Testing is just the process of
analyzing and running a program. Now, testing is a common way to
search for bugs; however, it's not the only way, and it also doesn't
address how you remove a bug once we know that one is there. With that
said, good debugging always starts with testing. Now, when we're using
testing to search for errors, we create what are known as test cases. A
test case is an input together with an expected output. Now, that input is
just one or more arguments for our function, and the output is what is
returned, provided that it is a fruitful function. If it's a procedure, we have
a slightly more broad notion of output, but it works pretty much the same.
So for this course module, we're only going to focus on fruitful functions.

Once we have a collection of test cases, we have now what is known as


a testing plan, and that's going to tell us how we carry out our testing.
The process that we're going to do here is very, very similar to what we
did in the course module where we were reading specifications. In fact,
let's look at a function for that module, which is the function
number_vowels(). Here we have our precise specification for this
particular function, and you'll notice that at the end of the specification,
we have several examples. Well, these examples are test cases. You'll

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
notice that here I have a sample input for number_vowels(), and I say
that, "Oh, you should expect that this gives us the answer 1 back. And
here is another input; in this case, we should expect that 'hate' should
give us the answer 2 back." So we could consider all of these together as
a testing plan. So, now we're going to show you, how do we take this
particular specification and we actually do formal testing. We're going to
take Python, we're going to start up Python, and we're going to import
this Python module. And we're going to just start testing out the function.

How do you test a function? You just call it with those particular inputs.
So I'm going to call number_vowels(), and we're going to do 'hat', and we
expect the answer to be 1. Great; that's the correct answer. 'Hate'; we
expect the answer to be 2. So far, so good. 'Beet'; we expect the answer
to be 2. Once again, so far, so good. 'Sky'; now we noticed that when we
called the function, we got the answer 0, but the specification said that
the answer should be 1. Well, when the specification and the function
disagree, well, the specification always wins. The specification is always
more important than the function definition. So this tells me that I actually
have an error somewhere down here in my code. Now, there's a lot of
code here; I don't know exactly where the error is, but I'm just going to go
ahead and sort of reveal the secret, which is I forgot to add a y down
here at the bottom. So now what I'm going to do is, I'm going to save this,
and I'm going to quit, and now we're going to start this process all over
again. Let's "import vowels". And you might think, "OK; now all we need
to do is we just need to test 'sky'; it's working properly, and we can just
continue on from there." This is not how we do things. Every single time
that you fix a function, you go back to the beginning, and you start
working on the plan again. Why is that? Because maybe when you fixed
one thing, you broke something else. So we always very methodically go
through all of the test cases in our testing plan, and it is only after we
pass every single one of the cases in our plan — which we do in this
case — that we decide that our testing is complete. This is something
that we're going to go into much, much more detail throughout this entire
course module.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Reviewing Terminology
5HYLHZLQJ7HUPLQRORJ\FRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Designing Test Cases
You have seen how test cases are created to search for errors. Now you
need to decide which test cases you want to use.

In this first video, Professor White describes how to determine which test
cases will best check your function for errors.

Video Transcript

In a previous video, we looked at the function number_vowels(), and we


spent a lot of work on its specification to make sure that it was precise.
As part of our work on the specification, we actually came up with a
number of test cases, and using these test cases as examples helped us
to understand what we wanted our function to do. Now, you'll notice that
we actually came up with a lot of examples; we have a lot of test cases
here. But this raises an interesting question, which is, are these enough?
And how many test cases are enough? Well, instead of actually just
answering the question about number, let's just look at why we chose the
tests that we did. Right away, you'll notice that what we did was we
chose a very simple test, right, which has just one vowel in it. When
you're coming up with your test cases, you should always pick the
simplest case first because if it doesn't work for simple test cases, then
it's not going to work on the more complex ones as well. But, this isn't the
only way to have a simple test, right? I mean, it has just one vowel, but
here's another test — say, "hot" — which also has one vowel. And you'll
notice that they're different. Here, I'm looking at the variable o, and here I
have the variable a. And just because I work on the variable a doesn't
mean that I work on the variable o. So that means yes, I should be
testing things that have just one vowel in them, but I should also be
testing them for each possible vowel.

Then, what you'll notice is that we've started to look at more complex
cases, right? So, "hate" and "beet", these are examples with multiple
vowels. There's obviously a difference between "hate" and "beet," namely
that one has an a in it and the other one doesn't. But more importantly,
there's another distinction, which is "hate" has two different vowels in it,

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
and "beet" has two vowels that are the same. So now I have to think
about all of the different ways that I can come up with vowel
combinations. After I've just been counting vowels, then I need to start
thinking about weird possibilities. Well, what do I mean by "weird
possibilities"? Well, y is unusual. Y at the end of a word counts as a
vowel, but y at the beginning doesn't, so I need to make sure that I have
tests with y at the beginning and not at the beginning so that I can know
that it works properly. And finally, I want to have a test where there's no
vowels, and I need to make sure that I can actually work properly when
things are missing. So you might just say, "Oh, wow, I'm just going to be
testing any sort of string." And it is true, right? You may find yourself
adding lots and lots of tests. But there is one important rule, which is you
should never test a violation of a precondition. What do we mean by this?
Well, let's say we thought, "Let's do the test number_vowels('12a')."
Here, we have a string, but you'll notice that this string actually violates
the precondition because the precondition says that w is at least one
letter and only letters. I have things other than letters here. You might
say, "This doesn't harm anyone. I see that there's an a here; I should just
return 1." But remember, we've talked about assumptions before. You're
only supposed to follow the specification exactly as it is said. Since 12a
violates the precondition, you have no idea what is supposed to happen.
Maybe it's supposed to crash. Maybe it's supposed to give me an
answer. There are no guarantees at all, so there's no way for us to write
an answer here. So therefore, we should never include this in the test
that we pick.

In this video, Professor White introduces the framework to follow in test


case design: the Rule of Numbers.

Video Transcript

In a previous video, we were looking at the function number_vowels()


and we came up with a lot of tests, right? And so looking at this, we go,
"All right; when can we stop? How many tests are actually enough?"
Because the problem is that we can't test everything. I mean, there are
an infinite possible number of strings. What we want to do is we want to
limit ourselves to tests that are representative. What we mean by that is,
each test needs to be significantly different in some way. That way, we
might think of other tests later, but they're pretty similar to ones we might
© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
have seen so far. If this sounds vague, that's because, yeah, this is art;
it's not a science. If it was easy, everyone would be able to test perfectly
and no one would ever have any bugs. It's one of those things that you
just have to learn with a lot of practice and it's one of the reasons why we
like to teach testing early. So, OK, to help us understand what the
problem is, let's try a little exercise. Let's come up with five test cases for
the function number_vowels().

All right. First we'll start off with 'hat'; that has a single vowel in it, so the
answer is 1. Let's try a little more interesting word, 'charm'. That also has
a single vowel in it; its answer's 1. How about 'bet'? This also has a single
e in it; the answer is 1. OK; let's try a little more complicated, 'beet'. Now I
have two es, so the answer is 2. 2 is fine; let's — how about three? Let's
try 'beetle'. So what we have now is five test cases. But the question to
ask yourself is, how many really different tests are there here? Think
about this a second; pause the video if you need to before we give you
the answer. OK; have you thought about it? Well, the answer is, even
though there are five test cases here, there are really only three different
tests. Why is that? Well, obviously "hat" and "bet" are different because
they each only have one vowel but they're different vowels. Just because
you work for a doesn't necessarily mean that the function works for e.
"Charm," however — OK; I mean, it's got a c and it's got an r in it, but all
this function is doing is counting vowels, so I don't really care about the
consonants in there. And if I'm just counting vowels, I mean, "charm" is
the same as "hat"; it's not really different. OK; well, I can still think that
"beet" is different than "bet" because multiple vowels are very different
than one vowel. But I would also say that "beetle" is pretty much the
same thing as "beet." And that's because, in general, once you know that
something works for two things, you probably know that it works for more
than two. What we're talking about here is the rule of numbers. When
you're testing, the numbers are 1, 2, and 0. Number 1, well, that's the
simplest test possible, right? And the idea is things like, oh, a single
vowel, right? And I want to do those first because if that fails, I can't do
the complex tests. Number 2 is when you start adding things that are
more than expected, right? More than a single vowel; in this case maybe
multiple vowels, all the different ways of combining them with the same
vowel or different vowels. Finally, 0 is what do you do when something is
missing? Maybe the word has no vowels in it, or maybe I have a y there
but the y isn't actually a vowel, so it's something that's missing.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
To see what we mean by the rule of numbers, let's look at a slightly
different function that's a little more easy. This is a function called
last_name_first(). And what this function does is it takes the string of a
first name and a last name and it gives it back to me rewritten, with the
last name, a comma, and then the first name. This is all the function
does; I don't have to worry about different types of vowels or anything like
that. The only thing that matters is, how do I separate the first name from
the last name? And the specification says that it can be one or more
spaces. So you'll notice that in this example we have one test case. This
is the number 1, right; the simplest thing possible with a single space in
the middle. Here we have the number for Rule 2. We have more than one
space. We could put two spaces here, three spaces here; it doesn't really
matter. You'll notice that we don't actually have a test here for the
number 0. Why is that? Well, actually, it's because the precondition says
that there has to be one or more spaces, so it's not possible for anything
to be missing here. Remember, we never test anything that violates the
precondition, and the rule of numbers is not an exception to that. So all I
do in this case is I test number 1 and test number 2. We can be pretty
happy that, yeah, actually this is a pretty good enough test for this
particular function.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Designing Test Cases
'HVLJQLQJ7HVW&DVHVFRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Designing Tests with Multiple Arguments
'HVLJQLQJ7HVWVZLWK0XOWLSOH$UJXPHQWVFRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Scripting Tests
When you consider testing, debugging, and retesting a function over and
over, you might wonder if there is a better way.

In this video, Professor White introduces a faster way to do this: the unit
test.

Professor White also introduces the IntroCS module. Documentation is


available on the next webpage. You can refer to it, as needed, throughout
the rest of the course. However, you do not need to read all of this
documentation, as we will not be using all of the features of this module.

Video Transcript

Up until now, we've been focusing on the theory of testing. But in this
video, we want to delve into the practice of actual testing. To help
ourselves understand, we're going to work with the function last-name-
first() again, because it's rather simple to test; it only has two test cases.
Remember that this is a function that takes a string that has two names,
and it's going to reorder them so that the last name is first, followed by a
comma. So let's think about how we would test this particular function.
Right now we have to type "python" to start it up. We have to import the
module. From that module we have to call the function last_name_first().
And we have to give it the input of our test cases. So we're going to do
the rule of one first with a single space, and everything is fine. Now we're
going to do the rule of two; I'm going to give it more than one space, hit
Return, and now we'll notice that actually there is a problem with this
function, right? It may seem like it behaves naturally, but the specification
makes it very, very clear that there are not supposed to be any spaces
before the last name, and here I have some spaces before the last name.
Remember we always read the specification exactly. So this is not
correct, and I have a function that doesn't work. We're not going to worry
about fixing this yet; we'll handle that in a later video. But let's keep in
mind what happens when we do fix it. What do we do?

Well, we have to quit Python. We have to start Python again, import the

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
module again, and we have to start this process all over. This is
incredibly annoying. And we're only talking about a function that just has
two test cases; I have to do this all the time? Look, we're learning how to
program. We're learning how to automate things. Why can't we just
automate testing? That's an interesting idea. What if we made a second
Python module, or even a script, that could test the first one? That's what
a unit test is; a unit test is a script that is used to test another module.
That script imports the module so it can test it, and inside of that script
you define one or more test cases, and it's just going to evaluate those
test cases. It's going to call the function on the input and it's going to
compare that result to the output that you expected. If everything is
working, it does nothing; it just prints out a clean bill of health. If it's not
working, well, it's going to try to give you some useful information.

Let's see this in practice. In this directory, I have both last_name_first()


but I also have number_vowels(), which is our function that we've seen
before as well. This function is actually implemented correctly.
test_vowels is a script that is used to test this particular function. So let's
try this. Remember, to run a script, we quit Python. We type the word
"python" and then the name of the script that we want to run, which in this
case is test_vowels. I hit Return, and it tells me that, oh, "Module vowels
is working correctly". The function passed all of its tests. Now let's look at
the module "name". For that I have a script called test_name. We know
that this one is not working correctly. And lo and behold, that's right; it
actually gives me an error message telling me that it expected to have
the answer in this format, but it's wrong because I have a bunch of extra
spaces that it wasn't looking for. And this is nice because if I want to run
the tests again, all I have to do is type a single command; I don't have to
do all of these steps step by step. So, how do I write a script like this?
Well, let's look at the script for test_name. Inside of this I import the
module "name" because that's going to contain the function
last_name_first() that I want to test. I also import another module. This
module is called IntroCS. This is a special module that is designed for
this course and its documentation is available on the course web page. It
contains some useful string functions as well as some other functions
that are beyond the scope of this course. But for this lecture, we're really
interested in one specific function called assert_equals(). And
assert_equals() is what we use for testing. So, to understand how it
works, let's step through a test case. Here I have an input, and I'm calling

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
the function. And so I'm taking the answer that that function gives me and
I'm putting it inside of a variable. That's where the answer actually is.
Now, I want the answer to look like this. So what assert_equals() is doing
is it's comparing the answer that I expect against the answer that I
actually got. You'll notice that this is a procedure; I'm not trying to assign
it to a variable or anything like that. So what does this procedure do?
Well, if the answer matches, it does nothing. To see that in practice, let's
look at the test script for number_vowels. You'll notice that lots of single
test cases here, and then there's a print statement down at the bottom of
the module. When we ran that particular test script, the only thing that we
saw was this final print statement; we didn't actually see anything here.
And that's because, look, if you pass the test, I don't need to know
anything more; I can just move on to the next test. However, if you fail a
test, then what it's going to do is it's going to stop testing immediately and
give me the line number that failed. So if we look at this error message
here, we will notice that at line 19 we failed. So we can see that this test
case is the one that failed; the one that uses the rule of two. And now I
can use this particular test case and help me to search for my error.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Script Tests
In the Codio project below, navigate to the page Overview of Exercise
1. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 1, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Read: IntroCS Documentation
This documentation is available for you to refer to as needed throughout
the remainder of the course. However, you do not need to read all of this
documentation, as we will not be using all of the features of this module.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Organizing Test Cases
There will be situations where you will be testing multiple functions and
need a way to keep them organized.

In this video, Professor White shows how this can be accomplished by


putting test cases inside of procedures.

Video Transcript

In the real world, we can have a lot of test cases. I mean, we've already
seen the function number_vowels(). And if we look at the test script for it
and we scroll through it, we can see, yeah, there are a lot of test cases
here. Well, this is nothing; there's not actually a lot of test cases here. For
my game design courses, I've written some software that my students
use, and because it's important to know that it's correct before I put it in
the hands of my students, I've tested it. It's not an exaggeration to point
out that there are over 20,000 test cases for this piece of software. Now,
that's not all for just one function; there are lot of functions in this piece of
software. But it does mean that I need a way of organizing all of these
tests. Now, the way we've seen that so far is we've created a separate
test script for each function. Here we have the unit test script for
number_vowels(), and here we have the test script for last_name_first().
Well, that's fine, but the problem is, is now I have to run each script for
each function. The way I got to 20,000 test cases is because I have a lot
of functions and I don't want to have to run each one separately. What I
really want is one script that runs it for all my functions. And so now
inside of that script, we have to have a way of separating the test cases
for each function. We do that with what are known as test procedures. So
here we're looking at a unit test script that tests both number_vowels()
and last_name_first().

You notice that I have a function definition here. This function is called
test_number_vowels(). It's different than number_vowels(). This is the
function that's defined here. Test_number_vowels() is a test procedure
whose purpose is to test the function number_vowels(). And the only
thing that's inside of the body of this function definition are my test cases.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Similarly, when I want to test last_name_first(), I have a separate function
called test_last_name_first(). It's not the same as the function
last_name_first(); it is a test procedure to test that function. And the only
thing in the body of this are my test cases. So, that's how I organize all
my tests; I've essentially created these test procedure definitions. But
remember, a function doesn't do anything unless you call it. So down at
the bottom of the file, I need to call these tests in order to execute them.
So let's see this in practice. I'm going to run this particular test, and you'll
notice that it crashed, because still, last_name_first() is not working
correctly. What happened was it actually crashed during these tests so it
didn't even reach any of the tests for number_vowels(). Now, right away
we can see an interesting thing about how we've organized our tests
here. Let's suppose I've decided that I can't find the bug for
last_name_first() and I just want to move on to other functions for right
now. Well, what I can do is I can put a hashtag in front of this function
call. And now I'm going to run it, and you'll notice that it passed all of the
tests.

Why did it pass all of the tests? Because I no longer have a function call
for test_last_name_first(), right? Putting a hashtag in front of it turns this
into a comment, and all comments are ignored. So the only test that is
actually executed is test_number_vowels(). And we know that function
works properly, so it passes all of the tests. So one of the things that you
notice is that I can easily turn tests on and off without just deleting the
tests. The tests are still here, but I've deactivated them. That's why I have
the separation of the test procedure and the call. It's also why you'll
notice that in our test procedures, we have these print statements. Notice
that I say here I'm printing last_name_first(), and here I say that I'm
printing — I'm testing number_vowels(). The reason why we do that is
because if you look on the screen here, I don't actually see a test for
last_name_first(). I'm reminding myself that I've deactivated that test. So
OK; now when I actually want to go test that, I'm going to remove the
comment, I'm going to run the script once again. Now you'll see that it is
actually running that test, because I have that print statement. But now it
crashes, and so I have to go working on this function once again.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Organize Test Cases
In the Codio project below, navigate to the page Overview of Exercise
2. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 2, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Debugging Discovered Errors
When testing code, locating an error is only part of the task. The next
steps are finding out exactly what the error is and fixing it.

In this first video, Professor White introduces white-box testing and


shows how it differs from black-box testing, which is what you have been
doing up until now.

Video Transcript

We've been working with this function called last_name_first() that has an
error in it. We haven't been able to remove the error yet, but we know
that it's there. Let's remind ourselves this is this function that takes a
string with two names in it and it reorders it so that the last name is first.
The reason why we know that it has an error in it is because we have a
test script. It has two test cases in it. One follows the rule of one; it has
one space between the two names. The other one follows the rule of two;
it has more than one space between the two names. When I run this test
script, you'll see that it crashes, and it's crashing on the rule of two; line
29 is the second test case. And that's great, but it doesn't actually tell us
what the error is or how to fix it. Well, to understand how to fix errors, you
need to understand that there are two different types of testing: black-box
testing and white-box testing. In black-box testing, the function is opaque;
it's a black box. You're not allowed to look at the function definition.
You're only allowed to look at what the function does. Well, that's what
we're doing here in these test cases; we're taking the function, we're
giving it an input, that's giving us a result, and we're comparing that result
with what we expect in order to test it. White-box testing, the function is
transparent. All of the tests and debugging take place inside of that
particular function. The way that we do that is through the use of print().
Sounds a little weird, so let's see this in practice.

Here we have the function definition for last_name_first(), and you notice
that I have a bunch of print statements that all have hashtags in front of
them. Well, these hashtags are essentially making them into comments.
Right now Python is ignoring the print statements. We call this

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
"commenting out" a line of code. What I'm going to do is I'm going to
remove these hashtags — this is called "uncommenting" — and that's
going to make it so that Python now pays attention to these print
statements. We're going to save, and we're going to run the test again.
You'll notice that I have a lot more information here on the screen. That's
because all of these print statements are being executed. In fact, we
have — one, two, three, four, five, six, seven — eight new lines here that
weren't there before. Now, by the way, you might think that's a little
strange because we've only added — one, two, three — four print
statements. But let's remind ourselves that we actually have two test
cases. So what we're looking at is two groups of four. These are four
print statements for the first test case, and these are four print statements
for the second test case. Now, we're only going to talk about how we
read these in the next video. But what I want to do is just think about at a
high level what we're trying to do with these print statements. You'll
notice that when we're writing our functions, right now, essentially all of
our functions are either assignment statements — like I have three
assignment statements here — or a return statement. There are only four
lines that are doing any of the work here. The print statements are just
used for debugging. The goal of white-box testing is to locate the exact
line with the error. Is it on this line? Is it on this line? Is it on this line? Or
is it in the return statement? Now, once you find the exact line, what do
you do? Well, you just look at that line really hard until you can find the
error.

There's no magic bullet there, and actually that's what you had to do in
an earlier assessment. You were given a function that had only one line
in it, and you had to look at it to find the error. But the key thing to
understand is, is that what's going on is very, very similar to black-box
testing. Each one of these lines is an assignment statement that changes
a variable. Here I'm changing "end_first", here I'm changing "first". The
print statement, I am printing out the variable that I have now changed.
And that's the idea, right; I expect that variable to be something, and
what I want to do is I want to print it out to see if it is indeed what I
expect. This is pretty much the same thing that's going on in black-box
testing. With that said, we don't use this all the time, right? Why do we
like to use unit tests as opposed to using print statements? That's
because print statements, you have to remove them when you are done.
If you remember, we had all of these things with hashtags in front of them

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
that were commented out. Why is that? That's because the presence of
print statements are not allowed. They're not part of the specification or
contract, so they're clearly a violation. They also slow everything down
unnecessarily. But finally, they're just unprofessional. The Apple App
Store will reject any app that you submit to it that has print statements
inside of it. So that's one of the reasons why it's not as ideal. With that
said, right, we don't have to completely erase them. That's what we've
done with the comments; we only have to turn them off. And this is a very
common thing that people do with debugging and testing. In fact, if you
look at a lot of open-source code, you'll notice that it is littered with print
statements that are written in comments so they're not actually active.

In this video, Professor White shows how to use white-box testing to


pinpoint the location of an error.

Video Transcript

In a previous video, we talked about using white-box testing and print


statements to help us locate the exact line that had the error on it in
last_name_first(). Once again, here we are looking at the definition of
last_name_first(). You'll notice that we have four print statements that
we've added to the body. When we run our test script, you'll notice that it
adds eight extra lines to it, and that's because it's four print statements
for one test, and four print statements for another test. In this video, we're
going to look a little deeper at these print statements and see how it is
that we can find the error in this particular function. So what we're going
to do is we're going to look first at the test that passed, the one that didn't
actually have a problem. This is the rule of one. The thing right away that
you'll notice is the very first thing we do is we print out the argument n,
and that's just because I want to be able to quickly tell which test I'm
looking at. Am I looking at the rule of one, or am I looking at the rule of
two in this case?

All right; so the next line is an assignment statement, and you'll notice
that I have a function call here, find_str(), inside of the module IntroCS.
This module IntroCS is provided for this course and we link the
documentation on the course web page. If you look at the documentation
for this particular function, you notice that what it does is it tells me the
location of one string inside of another. What I'm doing is I'm using it to

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
find the location of the space inside of the string n. If we were to look at
this string here, we'll notice that this space appears at position 6. So
when I print out "end_first", well, yeah, 6 is what I expect the answer to
be. On the next line I'm using string slicing at "end_first" to give me the
first name. And indeed that's what I expect. And then I'm again using
string slicing to get the last name, and that's what I expect. Every single
line, the print statements are what I expect, and that's great, right? I
mean, there were no problems for this particular test case. So let's look
at the problem test case and let's see if we can use it to find the error.

Here we have more than one space. Now, on this line, I'm just trying to
find the end of the first name. So even though I have multiple spaces,
that's really just the first space. And the first space is still at position 6, so
there's no problem there. And when I slice out the first name, I'm still
slicing that out correctly. The last name is different, however, right? Now
you'll notice that I'm getting all of these extra spaces at the beginning, so
this is where my problem is, and this is what I need to fix in order to fix
the function. OK; so let's ask ourselves, how do we fix this function? I
mean, the problem is, is that we have more than one space here. So I
can use this space to slice out the first name, but I need to use this space
to maybe slice out the last name. And that would require another search.
I could do that, but I don't want to do solutions that make my code more
complicated.

Is there an easy way to solve this particular problem? Well, let's go look
at IntroCS again, and let's go look at all the functions that we have
available to ourselves. And you'll notice that there's this function called
strip(). And the entire purpose of this function called strip() is that it
removes extra spaces that I don't want to have. So this suggests a really
easy way to finish this function. I'm just going to call "introcs.strip()" on
this particular string. I'm going to save it, I'm going to run the script again,
and now you notice that there's no error. Actually, the module is working
correctly. I still have the print statements, and remember we do need to
remove these, else Apple's not going to be happy when we submit
something like this to the App Store. So I'm going to remove it. And
whenever you make any modification to a function, even if it's just
removing debugging code, please remember to run the test one more
time to make sure that you didn't break anything. And there we've done it,
right? We've fixed everything. OK; so that's great. That's how we use

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
print statements to test and debug our functions. There's one more thing
that I'd like to say, which is, notice if you're looking at these print
statements, it's really hard to tell what's going on. I mean, we were able
to look at it really closely and sort of line this up with our code, but there
was no need for doing that. We could have made our print statements a
little more helpful to have us understand what's going on. To see what I
mean by that, here's an alternate version of last_name_first(), and what
you'll notice is that our print statements are a little more descriptive.
Instead of just printing out variables, we actually sort of combine them
with strings to make complete sentences. I'm going to change my test
script to use that particular module instead. So I "import annotate". I'm
going to call it "name" so I don't have to change a lot of stuff in my test
script. And now what we're going to do is we're going to run this particular
test script again. We have the error because this version of the function
hasn't actually been fixed, but notice how everything is a lot different,
right? I have a lot more helpful messages on the screen, and this is a lot
more useful when I'm debugging. Now, should you do this? It is helpful.
With that said, there are a lot of people who do very bare-bones print
statements just like this. It really is sort of a matter of personal choice,
and you should do what it is that you're most comfortable with.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Debug Discovered Errors
In the Codio project below, navigate to the page Overview of Exercise
3. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 3, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Tool: Fundamental Python Concepts 3

Click here to download the


Fundamental Python Concepts
Tool, if you haven't already.
Pages 4-5 cover all the content
we have seen in this module.

In this module, we explored how to test a function for errors. This


reference tool contains key Python concepts we've covered. Check out
pages four through five to help you review things such as identifying
errors and how to test a function.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Project Submission for Testing Functions
You have now completed all Codio exercises for the course module
Testing Functions. To submit the project, go to the page Complete the
Course Module in the Codio project and follow the instructions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Wrap-up: Testing Functions
In this module, Professor White explained how to check a function for
errors. You then examined how to design test cases to search for errors.
Professor White also introduced the unit test and the IntroCS module,
which are faster ways to test, debug, and retest a function. In addition,
you explored how to organize unit tests and how to debug discovered
errors.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module 4: Designing Functions
Module Introduction: Designing Functions
Watch: Motivating the Implementation Process
Watch: Implementing with Function Stubs
Activity: Implement with Function Stubs
Watch: Implementing with Pseudocode
Implementing with Pseudocode
Activity: Implement with Pseudocode
Watch: Testing with Function Stubs
Watch: Implementing Backwards
Activity: Implement Backwards
Watch: Implementing with Helper Functions
Activity: Implement with Helper Functions
Tool: Fundamental Python Concepts 4
Project Submission for Designing Functions
Module Wrap-up: Designing Functions

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Introduction: Designing Functions
You've seen the basics of how to write a function
definition with the correct syntax. You've also explored
how to call and specify a function. In this module, we'll
put all of this together to implement a function based on
an English description of what it should do. We'll look at
the role of function stubs, how to use pseudocode to
design a function, implementing backwards, and
implementing with helper functions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Motivating the Implementation Process
You've examined how functions work, including writing specifications and
running tests. Now it's time to move to the hardest part: implementing a
function. Implementing a function involves writing code from an English
description of what the function should do.

In this first video, Professor White demonstrates a typical workflow for


implementing a function and describes the two types of problems you
might run into.

Video Transcript

In the previous course modules, we've been showing you a lot of


technical details. We've shown you the basics of how to write a function
definition and what's the correct syntax. We've also shown you how to
call a function and what happens when you do call it. We've also shown
you how to test a function that has a definition. But all of these are very,
very different than implementing a function. When we talk about
implementing a function, we mean you're given an English description of
what that function should do, generally in the form of a specification. And
now it's your responsibility to write code that meets that specification. For
example, let's return to this function last_name_first() that we saw in a
previous course module. Here we have the start of a function definition,
and you'll notice that we have a complete specification that tells us what
the function should do. But we're missing the body. This function doesn't
actually do anything; I just have a comment here where I should actually
have some code. And the idea is, it's my responsibility to fill in this code.
Well, why is this such a big deal? Well, the analogy is like a word
problem in mathematics. It's one thing to know how formulas work and
how to plug in values. It's another thing when you read a word problem to
know what is the right math thing that I should be doing for this particular
problem. With that said, that's the real skill that earns people money
when it comes to coding, and that's why it's so important. That's also why
this is the focus of this course module. Now, throughout this course
module, we're going to be coming up with a lot of different strategies to
help you implement functions. But from the beginning, you need to

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
understand the types of problems that you might run into. The problems
that you run into in implementing a function we like to separate into two
categories: syntax errors and conceptual errors. Syntax errors are when
Python can't understand you.

Let's look at this module called errors.py, and you'll notice that I have a
function definition, and then I have some other Python code at the bottom
of the script. There are actually two syntax errors here. To see what we
mean, let's start up Python and let's "import errors". You'll notice that it
says, "SyntaxError: invalid syntax", and that's because I'm missing
something important here. What I've forgotten is that every single
function definition must have a colon after the header. I'm going to save
this. Now I'm going to try to import it one more time. Let's "import errors".
And now you'll notice that once again, we have another syntax error. It
tells me, well, it looks like there's something wrong with line 21. There's
actually nothing wrong with line 21. The reason why it's confused is I
forgot to close the parentheses on the previous line. And when you do
that, that confuses Python and it's not actually able to find that error until
the next line. Let's save it. And now I can actually import this module
properly. Syntax errors are nice. I could essentially do what I just showed
you, which is we keep trying to import it, and as long as I have these
types of errors, I go into the file and modify it and fix them. That's very
different than conceptual errors. In conceptual errors, Python does what
you say but not what you meant, right? So an example of this might be
you slice a string a little too early and you cut off a character that you
didn't expect, or maybe you used the slightly wrong argument in a
function. For example, in this function call, maybe I didn't mean to use 3;
I meant to use 4 instead. Well, it's not going to crash there, but it's still
going to be wrong. These are the harder types of errors to find, and these
are the things that we're going to create strategies to try to avoid.

In this video, Professor White shows you the strategy that you'll be using
to implement functions.

Video Transcript

The primary strategy that we're going to be using in implementing


functions is what is known as a testing-first strategy. The idea here is that
we want to write the test first. Here we are, and we're looking at the start

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
of the definition of last_name_first(), and all we have is the specification;
we haven't actually done anything with the body yet. However, what we
have done is we've gone ahead and made the unit test script for this
particular function, right? Here are each of my test cases, which take an
input and give me an expected output. The reason why I can do that is
we've taught you that all you need is the specification to write test cases.
You don't need to be looking at the function definition. And the idea here
is that by having the test first, that can help drive forward what it is that
we should be doing while we're writing the function.

Now, you don't have to have a full unit test; if you wanted to, you could
just do everything by hand, right? Starting up Python, importing the
module, and calling the module on the various inputs. Of course, in this
case, right now, it's not going to do anything because I haven't actually
implemented the function. So let's talk about how we would make forward
progress. The next idea is you want to take small steps. We want you to
do as little as you can at a time and to make use of what are known as
placeholders. We'll talk a little bit more about placeholders in a later
video. But the idea is that once you finish one step, we want you to test it
immediately.

Let's see this in practice here. What we're going to do is we're going to
search for the first space. So inside of the string n, I'm going to look for
the position of the first space. And I'm going to assign that to the variable
"end_first". What I'd like to do is I'd like to be able to test this right away.
OK; so fine. We're going to quit, restart Python, "import name", and we'll
call the function. What you notice is that, oh, I don't actually see what's
going on here. And that's because I haven't returned anything, right? I
could only test these fruitful functions that return a value. I haven't
finished the function; I'm not ready to return anything. Oh, but I can return
what I've done so far. So let's just "return end_first". Let's save. Let's quit.
Restart Python, "import name", last_name_first(), and ha! By running the
function and testing it, I can see right away that "end_first" is what I
expect. And now I can move on to the next line. So this is the idea, right?
We finish a step, we test it immediately using various techniques like the
one that I just showed you, and we do not move on to the next step until
we're done. All of the various strategies that we're going to teach you
throughout this course are really just variations and more advanced
versions of this idea.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Implementing with Function Stubs
In the Testing First strategy, you saw the idea of step-by-step testing on a
function. But how is it possible to test a function while still working on it?

In this video, Professor White explains that testing is done on an


unfinished function, known as a function stub.

Video Transcript

As we've said, our basic strategy for implementing a function definition is


to fill in a little bit at a time. But that means we need to start somewhere.
So what we're going to do is we're going to start with a function stub. A
function stub is a function that's unfinished but it can at least be called.
And the fact that we can call this function is going to allow us to test it
while we're still working. For example, let's look at the definition here of
last_name_first(). So you'll notice we have the basic structure of a
function definition with a header, and it's followed by a specification, but
there isn't really a body here. In fact, we have a comment that tells us
that we need to finish the body. However, there's still enough here that
we can call it. If we were to start up Python, "import name", and call the
function last_name_first(), it would say my name. It doesn't crash. It
doesn't do anything — that's because it's not finished — but it still doesn't
crash. And that's the purpose of a function stub: get something that I can
run without crashing even though it may not be finished.

So what is a function stub? Well, we have to have the start of a function


definition. We have to have the header. We have to have the thing that
defines it. It's just that, well, maybe the body is empty. However, it's a
little more complicated than that. The body can't be completely empty.
For example, in this particular definition right here, the body's not empty;
we have a specification. And specifications are indeed part of the body.
The way you know that is even though I haven't defined the function, I
can still get help on this function. I would type the "help" and
"last_name_first". You'll notice that the help menu pops up and it gives
me the entire specification. That's because, well, I didn't have the
function definition completed, but I did have the specification, and Python

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
read that and it stored the specification.

A more extreme example would be to look at a function definition that


literally had no body. So here we have an alternate definition for
last_name_first(), and you'll notice that there's no specification after the
header. Instead, we just have this hashtag comment that says, "Finish
the body". If we try to import this module, you'll notice that we get an error
message. This error message is a syntax error. Syntax error says,
"unexpected" — this is "end of file" — "while parsing". There's a lot of
technical stuff going on there, but what it just really means is it tried to
read a function definition and there's no body there. Hashtags are very
different than docstring specifications. Docstring specifications, while they
are comments, are not fully ignored; they're read by the help system.
Hashtag comments are completely ignored. So even though I have this
hashtag comment, I might as well have had nothing there at all. It's
completely the same.

So, how do we get around this? Well, in general, it's a bad idea to start a
function without the specification anyway because you need that
specification to get started. But there is another solution that you could
do, and that is to type the word "pass". Well, what does "pass" mean?
"Pass" just means do nothing. It's a placeholder when I don't want to do
anything but I still need to have a function body there. If we save this, and
now we try to import this module, we don't get an error. In fact, we can
now call the function last_name_first() inside of this module with no
problems whatsoever. "Pass" is a very powerful tool and it's one of these
things that we're going to use to sort of give us placeholders to
essentially put in a note that, "Hey, I need to do some work here but I'm
not ready to do it." In general, the best thing to do is to combine the two.
So, here we have our original definition which had the specification. I
don't actually have to put "pass" here; it's not going to crash. But still, why
don't we just put "pass" here? Now what this is going to be is it's going to
be a note to myself that I haven't actually finished the function and I'm not
going to remove it until I'm done.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Implement with Function Stubs
In the Codio project below, navigate to the page Overview of Exercise
1. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 1, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Implementing with Pseudocode
Conceptual errors are errors that, if you are not careful, can be
introduced early in the implementation process.

In this first video, Professor White demonstrates a technique to help you


avoid conceptual errors.

Video Transcript

When you're working on implementing a function definition, remember


that there are two types of errors that you can run into. The first are
syntax errors. That's where Python can't understand you. But if it can't
understand you, it's going to give you an error message. Hopefully,
you're going to be able to read those error messages, just run Python
over and over again, and be using that to solve and remove that
particular error. The more dangerous errors are conceptual errors. That's
where Python does what you say, but not what you meant. To get rid of
conceptual errors, what you have to do is you have to plan ahead of time.
You have to create an outline. This outline should consist of the steps
that you hope to carry out. But we don't want you to just write this outline
on a sheet of paper. We want you to write this outline in your function
definition as comments. This outline is what we call pseudocode. It's
English statements of what you want to do. But each statement of each
comment corresponds to something simple to do in Python. To make this
a little more clear, let's look at an example.

Here we're looking at the definition last_name_first(). We have our


header and we have our specification. We don't have a body yet, but
you'll notice that we have four comments — not just one — in place of
the body. And the idea is that these four comments are an outline of what
it is that I want to do. At the very beginning, I want to find the space
between the two names. We look at our example here, right; our space
between the two names. Then I want to get the first name, then I want to
get the last name, and then finally what I want to do is I want to glue
together the first and the last name with a comma in the middle. So all of
these are English statements that I can understand, and now my goal is

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
to take each of these English statements and turn it into a Python
command.

Let's try the first one right away. So if I want to find the space between
two names, there's a function called find_str() inside of the IntroCS
module. find_str() takes the string n, and I tell it I want to search for a
space. It's going to give me the location of that space inside of the string
n and it's going to give that to me as a value. Now I don't want to forget
that value, so what I've done is I've assigned it to a variable. And this is
roughly what we would do when we're trying to turn our pseudocode into
actual Python code. Let's do it a little more step by step for the next
example. I want to get the first name. Well, remember what I'm doing is
I'm working with this string n. Where does the first name start? Well, it
starts at position 0 and it goes all the way up to the first space. Well, I
remember where the first space is; it is currently stored inside of the
variable "end_first". That's going to give me an expression for the first
name, but expressions by themselves can't be evaluated; they have to be
part of a command. In this case, what I want to do is I want to take this
expression and assign it to a variable. So that's what I do, and now I've
assigned it to the variable "first". When implementing this definition, we
just keep following this example, taking each of these comments, turning
them into Python code, until we're finished.

Pseudocode is a high-level description of your program, similar to an


outline. In this video, Professor White shows how to effectively leverage
pseudocode when designing a function.

Video Transcript

Now that we've seen how to use pseudocode, implementing a function


seems easy, right? We just write out the steps of what we want to do in
English and then we take each of those lines in English and turn it into
Python. No problem. There's got to be a catch, right? Indeed there is a
catch, and the catch is the pseudocode that you write has to correspond
to Python, and preferably each line of pseudocode should correspond to
one line of Python. To see what the problem is, let's once again look at
our definition of the function last_name_first().

Here, we have four lines of pseudocode telling us all the steps that we

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
want to do. Well, there's no reason we couldn't have written it all as one
line of pseudocode. Here I have an English statement as a comment that
says, "Hey, I want to return the last name, plus a comma, plus a first
name." But if I could do all of this in one line, why am I trying to
implement a function called last_name_first() in the first place; right? So
obviously this is not something that's easy to do in Python, where those
original four statements that we had before all are very easy to do in
Python. Well, OK; so how can I tell? How do I know what it is that's OK
as pseudocode and what's not? Well, that depends upon the types that
are involved. Different types have different operations, and one of the
things that you need to do before you start coding is memorizing the
important operations that are available for that type and use those as
building blocks. In this case, let's talk about strings because that's what
we have here; a function that's working with strings.

So let's think about the building blocks that we have. Well, one of the
things that we can do is we can take a string and we can slice it up into
little pieces. So slicing. And in fact, one of the things you'll know right
away is, even though I didn't explicitly say it, these two lines of
pseudocode really are about slicing. In fact, it might have even been
better for me to explicitly note to myself that what I'm doing in this case is
slicing out the first and the last name. Another thing that I can do with
strings is I can glue them together. And again, if we look at the definition
— well, we'll just say "put" instead of — we'll say "glue" here because
that's what's really happening in the last line of the pseudocode. So we
have slicing and we have gluing. OK; is that all we can do? Well, no; we
actually have a lot of things at our disposal, particularly if we look at the
module IntroCS.

So here if we pull up the module IntroCS, we'll notice that we have a lot
of things that we can do with strings. And in fact, our Table of Contents
actually organizes them in nice ways together. I can do things like
change the uppercase and lowercase parts of a string. I can search in a
string. I can count the number of times that a character is with a string. I
can determine if a text ends with or begins with a certain part of a string. I
can look for the location of an element in a string. So all sorts of things
that I can do with searching. I can also pad a string. I can do things like
center a string, or left-justify it or right-justify it. I can even strip spaces
away from it. Finally, in addition to all of these functions, one of the things

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
that we can do is we can often cast from strings to a different type. So I
might have a string like 12, and then I can do something like convert it
from a string to an integer. All of these things are basic building blocks.
So when you're writing your pseudocode, what you want to be doing is
thinking what are the things that my building blocks can do? The slicing,
the gluing; all of the functions that are available to me in IntroCS, and
write those as part of your outline. And that's what's going to make it easy
to replace it with actual code.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Implementing with Pseudocode
,PSOHPHQWLQJZLWK3VHXGRFRGHFRGLQJH[HUFLVH

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Implement with Pseudocode
This activity is optional and will not be included in your course grade.

To start, navigate in the Codio project to the page Remarks on Exercise


2. Complete all of the instructions for this exercise, checking your work as
you go.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Testing with Function Stubs
You have now worked with function stubs and seen how they help you
verify that each step works the way you intended it.

In this video, Professor White delves deeper into function stubs, showing
you different ways to alternate programming with testing.

Video Transcript

As we've said, one of the things that we want you to do while


implementing a function definition is to interleave programming and
testing. The idea is after you write each step in Python, we want you to
test it immediately. Now that seems a little counterintuitive, right, because
if the function definition's unfinished, the answer's going to be incorrect.
Well, we don't want you to be worried about the final answer. The goal is
that we want you to be looking at the intermediate results and making
sure that they are what you expect. So the idea is that we want you to
take an input from your testing plan, call the function on that input, and
then look at what your function is doing in its body at each step and
making sure that each step is what you expect. Now, if we're looking at
the function while it's being executed, that means we need to do some
visualization. And one of the easiest ways to do visualization is with the
Python Tutor. Here we have the Python Tutor with the function definition
for last_name_first(). We've started to implement the function
last_name_first() but we haven't completely finished. We've only done
two lines. But that's enough for us to test. So you'll notice at the very
bottom here, we don't have a function definition; we have a function call,
and we're calling it with one of the arguments from our testing plan. We're
going to hit "Visualize" to look at what happens here, and now we're
going to step through the code. We're going to read the function
definition, and now we're going to call the function. When you call the
function, it's going to create a call frame. That call frame is going to have
a single variable in it, n, which is the parameter, and that parameter is
going to be the argument that we are testing. And now we're ready to
look at each line of the body step by step. So this first line is going to find
the space and it's going to sit it in the variable "end_first". We hit

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
"Forward", the variable is created, and you'll notice it is the number 6.
Well, if we look at the argument, yes, 6 is the position of the space.
That's what I expect the answer to be. Great. So we go on to the next
line, hitting "Forward". And now we're looking at the first name, and yes,
that's what I expect; it's my entire name, it hasn't been cut off, and there
aren't any additional spaces. Now we've reached the end of the function;
if we were to hit "Forward", the call frame would be erased and we're
done. But that's OK because all we're doing is looking at how much that
we've implemented so far, and since there haven't been any surprises,
we can somewhat be assured that we're on the right track. Now, you
don't always have the Python Tutor available. The Python Tutor isn't fully
featured; it doesn't implement all of Python, and so sometimes you have
to test directly in Python itself. Well, in that case, what you could do is
you could work with print statements, just like we've shown you how to do
with debugging, right? In each case here I had an assignment statement,
so a natural thing that I want to be doing is looking at the variable after
the assignment statement. So here I've just added some sort of print
statements, and now we can run this in Python. We'll do "python". We'll
import the stub definition. We're going to call this stub definition
"last_name_first" on my name as a test. And, you know, it's not finished,
but right away you can see that, oh, I'm getting pretty much what I
expect. I'm seeing the 6. I'm seeing my first name being done correctly.
So I'm happy. That's one way to do things. Though, remember, if you
approach it this way, you do have to comment out your print statements
when done because they are not part of the specification. An alternative
approach is, OK, instead of doing print statements, why don't we just
return a value? Well, we're not done yet; we don't know what to return,
but what we could do is we could just return the last thing that we've
seen. So here we have — I've implemented "end_first". Hopefully, I
tested "end_first" before moving on to the variable "first". Now I want to
be testing that "first" is working correctly. So I'm going to return it. To test
it out, I'll just import the module. And now I'm going to call it on my test
case, "last_name_first". And the answer is what I expect. Notice an
advantage of doing it this way. Because I've returned it, it's returned it in
the string format, and I can see exactly where the quotes are. So I can be
sure that there's no extra spaces here; something that I wasn't exactly
sure when I was doing the print statements. I could call this on other
names as well, like, say, Abe Lincoln. I can see that the first name is
working correctly. Doing a couple of these tests, I can be sure that I've
© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
done the first name correctly, and now I can move on to the next line of
code.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Implementing Backwards
When preparing to implement a function, deciding where to start can be a
daunting task.

In this video, Professor White explains how working in reverse order can
help you begin function implementation.

Video Transcript

When implementing a function, the first step is always the hardest. Many
times we give students projects to work on, and they come to us for help
saying, "Well, we don't really know where to start." And that's true even
when we've taught them techniques like pseudocode. Sometimes the first
step just really isn't obvious. Well, there are techniques that we can use
to get around those blocks. One of them is, why don't we think about
working in reverse? I mean, when you read a specification, it tells you
what it is that it wants you to return. So you know where you need to be
in the end; your final answer. Well, what if you started with that final
answer and worked backwards to figure out how to get there from what
you're given? Let's try to make this a little more concrete with a specific
example. Here I have a function called middle(). It's a function we haven't
seen before, but it's relatively simple. All it does is it takes some text and
it gives me the middle portion of this.

Now, we haven't implemented this function here; the body is largely


empty. But behind the scenes, I do have a version of the function that is
completed. So let's call this function to see how it should work when
we're done. If I give it a string like 'abc', the middle is b. If I give it the
string 'abcdef', the middle third is cd. The middle third is easy to
understand when I have a string whose length is divisible by 3, like, say,
three or six characters. But what if I have something that's only four
characters? Well, remember, everything is always given by the
specification, and my specification is very specific here. The middle third
starts at the length divided by 3, rounded down, and continues until two-
thirds of the string, rounded down. So if I look at 'abcd', that's the answer.
If I look at 'abcde', that is the answer. OK; great.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
So, now how do I go about actually implementing this function? You'll
notice that in my function stub it's not a completely empty body; I do have
a return statement here. This return statement returns a variable result.
Now, there is no variable result. If I were actually to import this module
and try to call this function, I would get an error, saying it doesn't know
what "result" is. What I'm doing here is I'm using this variable as a
placeholder to tell me what should I be doing in a previous line. OK; well,
what should I be doing? The purpose of middle() is to give me the middle
third of text. So one of the things I should be doing is cutting out the
middle third. Well, let's do that as an assignment statement. "result" is I'm
going to take the text, and now I'm going to cut out the middle third.
Where is the middle third? I don't know, but let's make some placeholder
variables, like "start" and "end". I don't know what "start" and "end" are
yet; I will come back to those later. But they're nice little placeholders for
me to know, "OK, this is what I should be doing; I should be cutting the
text."

Next, I'm going to define the "end". Well, this is the end of the middle
third. Reading the specification, that's two times the size of the string
divided by 3, rounded down, and remember that for integers the double
slash rounds things down when I define them. So that's just reading the
specification itself. And now for "start", it's the size of the string divided by
3, rounded down. Everything's looking good so far, but, however, I still
have a variable that I don't know what it is; this "size" variable. But the
size is just the length of the string. And now this length has a variable in
at "text", but "text" is a variable that I know. It's the variable that I was
given to start with, namely my parameter. So now, since I don't have any
more undefined variables, probably my function is done. Well, let's test it.
Let's "import mid2". I'm going to call it on all of the inputs in my testing
plan. 'abc', looking good. 'abcd', looking good. 'abcde', and now 'abcdef'.
So, I can be pretty confident that my function is actually correct, and that
was not a bad way to implement it. However, there was a drawback,
right? Notice that we couldn't actually test this function until we were
done with it, and that's because it would've crashed if any of these
variables were not defined. And that kind of goes against our rule of
interleaving, programming, and testing. So, this is not necessarily the
most ideal technique. But with that said, it is certainly acceptable if you
have no idea what to do at all. We're just saying that you should use this
technique sparingly.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Implement Backwards
In the Codio project below, navigate to the page Overview of Exercise
3. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 3, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Implementing with Helper Functions
The one drawback of the backwards approach is not being able to
intersperse testing and programming. However, this is possible through
another technique called top-down design.

In this first video, Professor White demonstrates the advantages of top-


down design, as well as some considerations in its use.

Video Transcript

In a previous video, we looked at designing with a backwards approach.


Now, what was nice about this approach? The idea was we could be lazy
in our design; if you didn't know what to do, you just turn it into a variable,
and then you can define that variable with an assignment statement in a
previous line. Now, the problem with the backwards approach was we
couldn't do any testing until we're completely done. But what if we could
take that idea, this idea about designing lazily, and roll it forwards? By
working forwards, hopefully we could combine it with incremental testing
and get the best of both worlds. All right; to see what we mean by this,
let's return to our function last_name_first(). When we're working
forwards, we're going to make some pseudocode here. And the problem
that we might run into is the pseudocode that we've written is not
something that we easily know how to convert into Python. We might not
know what is the Python for finding the first name. But we know what the
first name should look like, and so that means we can clearly specify
what we want. And if you can clearly specify it, that means you can write
a new function, like, say, a function called first_name() that we have
specified here. And here we have some code for finding the first name;
it's very similar to the stuff that we did when we've been working with last
name before. We just find a space and slice everything before that
space.

That's great, but this is a new function; it's not the original function that
we were trying to solve. But that's OK; we can use this new function to
help us solve the first. So if we want to find the first name, we just call the
function first_name() on our parameter n. And now we can see how it is

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
that we design lazily. If I want to find the last name, I'm just going to
make a new function called last_name() and call it. And I don't exactly
know how to implement the last_name(), but I do have a specification for
it. So I can create a function stub, and now I can move on. And the nice
thing here is that I have no undefined variables. So that means that I can
actually test this function, what little I've done so far, without actually
having it crash on me. In fact, I can even go a little further; I can start
saying. "Hey, let's return not just the first name or the last name, but let's
go ahead and glue them together in the final return statement so that I
can look at both of these values in my testing."

OK; let's try this. Let's start Python. Let's "import name", and now let's
call last_name_first() on one of my typical test cases, using my name.
And notice it doesn't crash, and it's because everything's working so far. I
have the first name, I have a comma. I don't see the last name, but that's
because I'm not ready to actually implement the function last_name();
correct. But I already noticed that I have one error right away; this is why
we always test while we're writing. Notice that there's no space after the
comma. So I'm going to want to go ahead and make sure that I add a
space there and then test this once again. What we're talking about here
is what is known as top-down design. When you're working on a function,
you're given the specification for that function. And you can't change this
specification at all; changing this specification would break your team.
But what you can do is you can split up this specification into little
problems; things like finding the first name, or finding the last name. And
each one of those little problems becomes naturally its own function. And
now you can define the specification for each function and implement it
on your own. As a word of warning, don't go overboard on this technique,
right? We don't want you writing functions where the actual body of the
function was a single line. Because if you could write it in one line, why
do you really need to have that function in the first place? The general
rule of thumb for these types of things is, do it if your code is getting
really long. I personally have a one-page rule. If the code is going more
than one page, then what I should do is I should take that code and break
it up into different functions. Another reason why you might do this
technique is if you find yourself repeating yourself a lot. If you see the
same code over and over again, then take that code and replace it with a
new function call of your own.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
In this video, Professor White shows some tips to keep in mind when
using top-down design.

Video Transcript

As we saw with the idea of top-down design, we're given a function that
we want to implement, and we do that by breaking it up into smaller
functions that we implement as well. So, once again, we're looking at the
definition for the function last_name_first(), and we've broken this up into
two smaller functions called first_name() and last_name(). And now what
we've done is we've separated those, and we're going to implement
those on their own. When you do this approach, you also want to make
sure that you test these new functions in addition to the first one. So here
I have a unit test script for testing the function last_name_first().

Here is my test procedure for last_name_first() with my test cases, and


making sure that the answer that I get is equal to what it is that I expect.
But this is not the only test procedure that I have in this file. You'll notice
that I also have test procedures for the function first_name() and for the
function last_name() as well. So when I'm testing the first name, I need to
make sure that the answers that I get are what I expect. And when I'm
extracting the last name, I need to be sure that the answers that I get are
what I expect.

Finally, down at the bottom, when I'm writing my script code, remember,
every single unit test, it's not enough to have a test procedure; we also
have to call the test procedure to make sure that the tests are invoked.
You'll notice that we've called the three test procedures. And the order
here is particularly important. I've called first_name() first because that
appeared first in the body of our definition for last_name_first(). Here, it
appears first in the body of the definition, so I need to make sure that I
test it before I go any further. Then I can test the function last_name().
And then finally, when I'm sure that all of those are working, now I can
test the function together. Let's try this as a test script. We'll do "python
test_name.py". And right away, we see first_name() is working correctly.
We passed all the tests. However, last_name() is not working correctly,
so it's not even going to go ahead and start testing last_name_first(); it's
going to expect us to fix this function before we move forward.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Implement with Helper Functions
In the Codio project below, navigate to the page Overview of Exercise
4. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 4, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Tool: Fundamental Python Concepts 4

Click here to download the


Fundamental Python Concepts
Tool, if you haven't already.
Pages 5-6 cover all the content
we have seen in this module.

So far, we've examined how to implement a function based on an English


description of what it should do. You also took a look at the role of
function stubs and practiced using pseudocode to design a function. This
reference tool contains key Python concepts we've covered in this
module. Check out pages five through six to help you review different
ways to design functions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Project Submission for Designing Functions
You have now completed all Codio exercises for the course module
Designing Functions. To submit the project, go to the page Complete
the Course Module in the Codio project and follow the instructions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Wrap-up: Designing Functions
In this module, you explored how to implement a function based on an
English description of what it should do. You also took a look at the role
of function stubs and practiced using pseudocode to design a function.
You also explored how to implement a function backwards and
implement with helper functions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module 5: Enforcing Specifications
Module Introduction: Enforcing Specifications
Watch: Reading Error Messages
Watch: Interpreting Error Messages
Activity: Interpret Error Messages
Watch: Asserting Preconditions
Activity: Assert Preconditions
Watch: Designing Error Messages
Activity: Design Error Messages
Watch: Identifying Trade-offs
Watch: Enforcing with Helper Functions
Activity: Enforce with Helper Functions
Tool: Fundamental Python Concepts 5
Project Submission for Enforcing Specifications
Module Wrap-up: Enforcing Specifications

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Introduction: Enforcing Specifications
In this module, Professor White explains in depth about
error messages, how to read them, and how to determine
responsibility. You'll also look at how to use assert
statements to help with identifying errors, how to design
error messages, and enforcing preconditions with helper
functions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Reading Error Messages
When code crashes, the information that enables you to find and fix the
problem is in the error message.

In this video, Professor White shows how to look at the trace of an error
message and find the source of the problem.

Video Transcript

One of the important things about specifications is they help us assign


responsibility. When the code crashes, who's responsible? Who needs to
fix it? Unfortunately, this is not always immediately clear, right? We have
to read and interpret the specification. But more importantly, we have to
compare it with what actually happened. That means that we need to
know how to read error messages, because error messages tell us what
happened. But there's a lot of hidden detail there. To see what we mean
by that, let's look at an example. Here we have a script called "nested".
"nested" is several functions defined inside of it: function_1(),
function_2(), function_3(). We call it "nested" because many of the
functions call each other; function_1() calls function_2(), function_2()
calls function_3(), and function_3() — then it does some actual
computation. Finally, down at the bottom, I have a call to function_1().
And that's what makes this a script, right; these print statements down at
the bottom are going to call all of these functions. So let's run this script.
And right away we see that it crashed, and we got an error message. But
an interesting question is, what exactly is the error message here; right?
Often when I'm trying to help students, one of the things I ask them is,
"OK; so tell me what the error is." And they'll look at this, and they'll say,
"OK. Well, the error is 'ZeroDivisionError; division by zero'." The problem
is, that is not the error message. Everything starting with the word
"Traceback" is the error message. And we need all of this information to
help us understand what's going on.

So what is being represented here? Let's remember how functions work,


right? function_1() calls function_2() calls function_3(). When functions
call each other, then they are arranged together in a stack. Let's look at

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
the Python Tutor. Here we're looking at the script once again, and we've
reached the point where we're at line 28, and we're one line away from
the error. If I were to step forward, you would notice that I would run into
the error. What's going on here is, I have called function_1(), function_1()
has called function_2(), and function_3() is now being executed. It's
what's going to run into an error. All of these call frames right here are
sitting on top of the call stack. The printout in the error message is what
is known as the stack trace. We may not have the Python Tutor; we may
not be able to visualize all of these things. But it gives us enough printout
so we know exactly what is going on in the entire call stack. So in
particular, you'll notice that it tells us we ran into an error on line 36. Now,
that error is inside of the definition of function_3(). We pull up the
definition, side by side. We can see line 36 inside of function_3(). Now,
how did we get to function_3()? Well, we had a call to function_3(), and
that occurred in line 26 of function_2(). And here we are. That
function_2() was called on line 16 of function_1(). And here we go. And
then finally, function_1() was called on line 41. Now you notice that we
have two calls to function_1(); one on line 40 and one on line 41. The
important thing about the stack trace is it's telling me exactly which one of
the two is it that called the problem. Because that's what can happen,
right? I might have a function, I might be calling it multiple times in
different places. I need to know exactly which function call is it that
caused the problem. Together, all of these things together help me
visualize the call stack that I would look at if I were, say, looking at things
in the Python Tutor. And that's what I'm going to use to help me to figure
out what's going on.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Interpreting Error Messages
When a function crashes and multiple developers are involved, it may not
be obvious who is responsible for fixing the problem.

In this video, Professor White shows how a step-by-step examination of


the stack trace and the specification can pinpoint the cause of the crash.

Video Transcript

Let's remind ourselves that when we're reading an error message, the
purpose is to help us understand where the responsibility lies. When
code crashes around a function, remember that there are two people
involved: There's the person who defined the function, and the person
who called the function. And we have to figure out whose fault it is and
who must fix it. So, let's look at this example here where I just have two
functions. I have function_1(), which calls function_2(); function_2(),
which does some computation. At the bottom of this script, I have a call
to function_1(). Let's run this script, and we're going to see that this
crashes. And we're going to get a complete stack trace here, right,
because at the bottom I call function_1(), which calls function_2(), and
then function_2() does some computation. The important thing to
understand is the actual error can be on any of the three lines that are
presented here, and that's the reason why we have to have the entire
stack trace when we're figuring out where the responsibility lies. So how
do we approach this? Well, the idea is we start from the top. In this case,
we're looking at the top of the stack trace, which is line 32. Ironically,
that's the bottom of the file, right; it's the call to function_1(). And what
we're going to do is we're going to look at this function call, and we're
also going to look at the arguments. In this case, we see right away that
the arguments are 1 and 0. Sometimes when we see the arguments,
they're variables, so we're going to have to have some form of
visualization, either using print statements or the Python Tutor, but the
key thing is we need to know exactly what the arguments were.

Once we know what the arguments are, we verify that the precondition is
met. If it's met, then there can't be anything wrong with that line, because

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
that's the point of a precondition. As long as the precondition is satisfied,
the function is supposed to work. So here I'm looking at 1 and 0; they are
both numbers. And if we look at the precondition for the specification for
function 1(), that's what it says. I haven't done anything wrong, so there
can't be anything wrong with line 32. OK; so function_1() calls
function_2(), and this is line 18, as I can see, and that's the next line I
need to be looking at. Now you'll notice that it's x and y. Well, I can
remember that, well, x and y got whatever x and y was before, namely 1
and 0, so I don't need to print out anything or visualize anything; I can be
pretty sure that these are 1 and 0 once again. OK; well, they're numbers,
and that's what the precondition of function_2() says. It says as long as
these are numbers, they have to work. So, there cannot be anything
wrong with line 18 either. Well, there's only one line left; it's line 28. And
so therefore, line 28 must be at fault. And indeed that's correct, right,
because the precondition for function_2() promised that it works
whenever x and y are numbers, even when y is 0. But you can't do this
division this way whenever you have y is equal to 0.

The important thing to understand is, is that this highly depends not on
the code, but on the specifications themselves. Let's look at a slightly
different script. This script is almost identical to the first one. And in fact, if
we were to run this script, you will notice that we get exactly the same
error message as the first one. There's the call on line 32, there's a call
on line 18 in function_1(), and then there's line 28. The only difference in
this case is that the precondition for function_2() is different. It says that y
doesn't just have to be a number; it also has to be greater than 0. So
actually, the error is no longer on line 28. Now, the error is on line 18, and
this is why specifications are so important. Line 18, I have a call to
function_2(). Inside of x is the value 1, inside of y is the value 0; that
violates the precondition for function_2(), and so now this line of code is
at fault and this is what must be fixed. Finally, let's look at a third file.
Once again, it looks exactly like the previous ones we've looked at. If we
were to run this script, it will give us exactly the same error message
once again. The only thing that has changed is the specification for both
function_1() and function_2(). Now for both functions, I'm specifying that
not only must y be a number, but it must be greater than 0. So now, the
error isn't on line 18, it isn't on line 28, but it's actually on line 32. And this
is why we need to combine looking at the stack trace in our error
message with the specifications to figure out what the correct approach

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
is.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Interpret Error Messages
In the Codio project below, navigate to the page Overview of Exercise
1. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 1, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Asserting Preconditions
You've seen how the source of an error can be found by examining the
stack trace and comparing it to the specification.

In this first video, Professor White shows you how to simplify reading the
stack trace, using a new type of command called an assert statement.

Video Transcript

So we've talked about how while we're reading an error message, we're
using this to assign responsibility and figure out exactly which line of
code to fix. But to do this, we have to go through the entire stack trace
and compare it to the specification at each step. It's going to be a little
tedious, so we'd like to know, how can we make this easier? Well, what if
we could control the error messages? The idea is that what we want to
do is we want to write exactly what the responsibility was directly into the
error, and that way we would only need to look at the error message and
not the full stack trace. The way we're going to do this is with assert
statements, which are a new type of command in Python. Let's start
Python. To write an assert statement, it looks like a return statement. I
write the word "assert", and then after "assert", I'm going to write an
expression. But the expression that I write needs to be a Boolean
expression; something like "assert True". Now I'm going to hit Return.

You'll notice that nothing happened, and that's the way "assert" works. If
the Boolean expression is true, nothing happens. On the other hand, if I
were to write "assert False", you'll notice that it crashed, and it gave me
an error, and it told me that the error was an assertion error. Now, there's
no error message here, but I could actually create one if I wanted to. And
the way that I do that is I'd write my assert statement, and then I write a
comma, and then after that comma, I'm going to write a string with my
message. I'll say, "You lose". Now I hit Return, and now you'll notice that
I have an error message, and it tells me directly in there that, OK, I have
a problem. Now, this may seem sort of cute, but why is this useful? OK;
well, let's try to do something a little more interesting. Let's suppose that I
have a variable x, which I'm going to assign to a value of 2. And what I

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
want to do is I want to assert that x is greater than 1. And now what I
want to have is an error message, and the error message that you give it
needs to be, well, maybe what I want to assert, now that x is greater than
1, didn't happen. So I want to assert that "x is out of range", OK? I'm
going to hit Return. Nothing happened. That's because x is indeed
greater than 1; no problem. But if I were to change this to assert x is
greater than 3 and hit Return, you notice that I get an error message, and
it tells me that "x is out of range". That's what my problem is. If you
notice, this looks kind of familiar, right? What I'm doing is I'm using assert
statements to verify that a fact is true. If the fact is true, nothing happens.
If the fact is not true, it's going to crash, and it's going to give me an error
message. This is really similar to the function assert_equals() that we've
been using in all of our unit tests. The thing that's different is, is that this
is a little more versatile, right? And this is something that I don't have to
just put into a unit test but I can actually write in my code, and it can help
me to understand the error messages a lot better.

In this video, Professor White demonstrates a technique known as


enforcing preconditions that helps assign responsibility in the event of a
crash.

Video Transcript

So now that we've seen how to use assert statements, let's use this to
help us assign responsibility when we actually have a crash. What we're
going to do is what is called enforcing preconditions. And the idea is
inside of our function body, what we're going to do is we're going to have
assert statements for all of our preconditions. The idea is if any
precondition is violated, it's going to crash immediately, and the message
is going to indicate what the problem is. To see this in practice, let's look
at our function last_name_first(). We've actually decided to make things a
little simpler here with our precondition and only allow a single space.
We're not going to handle the multiple-space case. As you can see, this
keeps our precondition fairly simple; we're only requiring that n is a string
and it has a single space in it. This function is no longer a stub; it has
been fully implemented, and here is the body of the function.

But in addition to that, you'll notice that we have multiple assert


statements here. What these assert statements are doing are checking

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
and verifying that the precondition is satisfied; namely, I'm asserting that
the type is a string, and I'm also asserting that there's exactly one space
inside of it. OK; so let's see this in practice, and let's do a function call
that violates the precondition. I'm going to give it not a string, but instead
a number. I've written this in script format with the definition at the top
and the call at the bottom, and so let's run this script. And you'll notice
that we get an error message. And the error message tells me right away
that I have a precondition violation.

Now, OK, so where is the line with the error? Well, line 26 is the line that
generated the error message, so that can't actually be the problem. The
problem line is actually the line before it, which does the function call.
And so now this is nice; when I see something that is a precondition
violation, I don't have to look at the entire stack trace. I don't look at the
bottom of the stack trace, but I look at the one line just above the bottom
of the stack trace, and that is where the error message is. And this
makes it a lot easier to figure out what's going on. That's one advantage
of enforcing preconditions. Another advantage of enforcing preconditions
is it makes undocumented behavior completely impossible. Let's look at
another example. Here is a function called to_centigrade() that we've
actually seen before. I'm going to remove the assert statement that we
have here in the definition, I'm going to start up Python, and I'm going to
import this module and call to_centigrade(). Remember, this function
converts things in centigrade to Fahrenheit. There we go; 100 degrees
Fahrenheit is 37 degrees Celsius. One of the problems that we see with
this is, well, I could call this with an integer, and I'm still going to get an
answer. However, technically what I just did is wrong. Because the
precondition clearly says that x has to be a float, and I called it without a
float but it didn't crash.

This is an example of undocumented behavior. If I add an assert


statement, where I make sure that it indeed must be a float, now if we
repeat this process again — "import temp", let's call to_centigrade() —
now it's going to crash with telling me that it's a precondition violation. So
therefore, by adding these assert statements, only valid calls to the
function will execute normally. And this is generally considered to be a
good thing, because one of the problems is undocumented behavior can
metastasize throughout your code. It might be working now, but later on,
while the code is running, it might cause something bad to make it worse.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
So the nice thing about assert statements is that we can shut it down now
before it causes any more problems.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Assert Preconditions
In the Codio project below, navigate to the page Overview of Exercise
2. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 2, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Designing Error Messages
At this point, you've worked on ways to detect where code has broken
and identify responsibility. The next step is writing effective error
messages.

In this first video, Professor White shows how to design error messages
that are helpful to users.

Video Transcript

We've seen how to use assert statements to enforce our preconditions,


but what we haven't really looked at yet is how do we design helpful and
useful error messages? To see what we mean by that, let's return to the
definition of our function last_name_first(). You'll notice that we have two
assert statements here that are used to assert our precondition; namely
that the type of n must be a string, and in addition there must be exactly
one space there. We also have specified some error messages.
Remember that when we have an assert statement, once we have a
comma, after that we provide a string, and that string is what is displayed
when that particular precondition is violated. To see that in practice, let's
start up Python. Let's import this module which is called "name0", and
now I'm going to call last_name_first() from this particular module. And
I'm going to give it an argument that violates my precondition. Instead of
a string, I'm going to give it a number. And here you go; it tells me that
the precondition is violated. But that's not particularly useful, and if I'm
just using this function for the first time, I might not know exactly what it is
that I did wrong.

So to see how we might make a better error message, let's look at


another version of this module, which we're going to call "name1". Once
again, you'll notice that we have assert statements here that are
enforcing our precondition, but you'll notice that our error messages are
slightly different. Instead of having just a very simple string in quotes after
the comma, I actually have a string expression. You'll notice that we're
taking the argument n and we're gluing it together with this message to
tell us that n is not a string. Let's try to see how this one now works.

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
We're going to import this module, which is called name1. I'm going to
call name1 with last_name_first(), and now we're going to give it the
argument of 12. And now you'll notice that we got a much, much more
helpful error message telling us that, wait, 12 is not a string, so that
argument is not going to work. When we did this, notice that we used the
function str(). That's because n is not actually a string, and if I wanted to
glue it to another string to produce an error message, I have to turn it into
a string first.

You'll notice that I don't actually do that in the later error messages; in the
later error messages I'm just gluing n together directly to the string. Why
is that? Well, if you passed this particular assert statement, I know that
you're already a string, so I don't actually have to convert you to a string,
and now I can just glue n directly to the error message itself. There's
another thing that's going on with these error messages, which is,
remember that we only had one assert statement here in the previous
example; namely just a count that there's exactly one space there. This
time we've actually split it up into two different assert statements, and
that's because I wanted to display two different error messages. To see
this in practice, let's suppose we call this function. We're going to give it a
string, so that part is OK, but while we're going to violate the precondition
by having no spaces inside of it at all. Now you'll notice that we get an
error message saying that, "Hey, this is missing a space." And that's
because this particular search statement failed. On the other hand, if I
were to give it something with, say, my middle name — so now I have too
many spaces — now I get a different error message. So before, in the
first file, you notice that we only needed two assert statements in order to
do the job, but sometimes we like to split up our assert statements
because it allows us to have more helpful and useful error messages.

In this video, Professor White introduces a new function that helps error
messages provide users as much information as possible.

Video Transcript

We've seen that we've been designing our error messages for our assert
statements that sometimes we actually have some fairly complex string
expressions. In this case, where we're asserting that the type of n had to
be a string, you'll notice that we wanted n to be part of the error

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
message, but to do that, we had to first make sure that n was indeed a
string and then we could glue it to the rest of the error message. This
creates another interesting question. Let's look at the following example.
I'm going to create an error message, and what I'm going to do is I'm
going to take a variable "var", convert it to a string, and then say that
particular variable is invalid. So here's my error message: "2 is invalid".
Now, the question is, just looking at this error message, do you know
what was inside of the variable "var"? Was it an int, like, say, the number
2? Or, well, there's another number type, so was it a float; namely 2.0?
Or is it actually a string? Remember, I could have '2' inside of quotes. In
fact, you may look at these three examples and you just may say it's
impossible to tell. Well, it's one of those four, so think about it, pause the
video, and then we'll tell you the answer.

OK; have you thought about it? The answer is, it is impossible to tell. In
this particular case, "var" was actually the string '2'. And why is that?
Because when you take the string function and you apply it to something
that is already a string, like '2' inside of quotes, you're just going to get
the same answer back. And so that's the reason why if I have "var" as the
string '2', and I take that and convert that to a string, it's just going to look
like that. On the other hand, if I take "var" and set it equal to the number
2, I'm going to get the same message, and that's because it's taking that
2 and it's turning it into the string with quotes. This is slightly unsettling.
When we have error messages, we want the error messages to give us
as much information as possible. We may not be completely aware of
what are stored inside all of our variables at each step, so we'd like to
have that information in the error message. To do that, we're going to
introduce another function. This function is called repr(), or I like to call it
"repper." It stands for "representation." And just like the string function, I
can use it to turn expressions into strings. So if I call repr() on the number
2, it's going to give me the string '2'. However, it does something slightly
different on strings. If I'm going to call repr() on the string '2', you'll notice
that this time we've still got a string — actually, this string is going to
begin and end with double quotes — but now what it's done is it's taken
the single quotes and it's put them inside of the string as part of the
string.

Now why is this useful? Let's think about using repr() in our error
messages. So I'm going to make an error message using repr() instead

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
of str(). I'm going to glue it together to "is invalid". And now we'll look at
the message. OK; this time, you'll see it was this before, and that's
because "var" is currently an integer. But if I make "var" equal to the
string '2' and now redo the message, now you'll see that it's actually put
the quotes around '2' inside of the error message. So I can see that in
this case, the variable must have been a string, and in this case the
variable must have been an integer. So I can get full information about
what's going on from the error message. That's the reason why, when we
look at our function last_name_first(), this is actually how we want to
design our assert statements. Instead of using str() for error messages,
we want to use repr(). And that way I'm going to get my quotes around it
if it's a string; and if it's missing quotes, it can't be a string. Let's see this
in practice. Let's "import name2", let's call last_name_first(). We'll do it
with 12, which is a number. Again, you'll see that 12 is not a string. It was
the same as if we had str(). But now let's actually see the more
interesting feature. Let's call this on my name. And I'm going to call it
without a space. Now you notice that I get an error message and it tells
me, "Aha, 'Walker' is missing a space." And the nice thing about that is
that I'm looking at the entire string and I can see where the string begins
and ends because the quotes were actually included in this error
message. To see why this is important, let's add a space after this. In
fact, let's add two spaces. Once again, I'm getting an error message, but
now I have an error message because I have too many spaces. And the
only way that I can actually see those spaces is because the quotes are
here. And I can see, "Wait; there's actually some mysterious extra
spaces before this quote." If we had done this in a previous version of the
module, which only used the str() function, it would be unclear what's
going on. If I do last_name_first() again of my name with two spaces,
now you'll get an error message, but I can't immediately tell what's going
on because were these extra spaces just an artifact of the error
message, or are they actually part of the string involved? Whereas, in the
error message that I had previously, this is absolutely clear because I can
see the quotes.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Design Error Messages
In the Codio project below, navigate to the page Overview of Exercise
3. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 3, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Identifying Trade-offs
You've seen the benefits of enforcing preconditions with assert
statements. However, there can be some drawbacks.

In this video, Professor White describes a way to identify the biggest


errors in your code while not hindering its performance.

Video Transcript

Enforcing preconditions can actually be pretty tricky. To see what we


mean by that, let's look again at our function last_name_first(). But in this
case, what we've done is we've changed the specification about a little
bit. In the past when we've been talking about enforcing preconditions,
our version required that there was exactly one space between the two
names. But now we're going to look at a different version of
last_name_first() that actually allows multiple spaces as long as all of
those spaces are contiguous in between the two names. Obviously, this
is going to make our precondition a lot more complicated. And that's what
the problem is. Sometimes, figuring out how to write the preconditions as
assert statements can be complicated. It could even be more
complicated than writing the code for the function itself. More importantly,
sometimes assert statements are just too expensive.

What do we mean? Well, checking the precondition with the assert


statement takes time, and that time slows down your function, and you
might want your code to run fast. So why are you slowing down your
code if you're confident that there are no bugs there? In the end,
remember, only the specification matters. Asserts are never required.
Right? The asserts were there as a convenience. They were used to help
us figure out whose responsibility it was. But in the end, the specification
is a contract and it is what determines who is at fault. Now, you might ask
yourself, well, how about a compromise? We might not be able to enforce
all of the preconditions, but what if we were able to handle the simple
parts? Let's break the precondition up. And what we're going to do is
we're going to assert the things that are easy to check and omit the
things that are hard to check. So that'll give us some minimal

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
enforcement. It'll allow us to identify the biggest errors. But some of the
more obscure things or hard to check things will go unchecked and we'll
just catch them in the system some other way.

To see this example, here we have two assert statements. Now, we're
asserting that n is a string. But when it comes to the number of spaces,
all we're doing is asserting that there must be one space there. We're not
doing anything about the number of spaces or whether or not the spaces
are right next to each other. So let's see what type of behavior this
causes. We're going to import this particular module and we're going to
call last_name_first(). We give it, say, the obvious wrong argument, like a
number. It's going to tell us that that's not a string and it's going to give us
an error message. We'll get another obvious error where there's no
spaces inside of it. Now it's going to tell us that it's missing a space. On
the other hand, let's suppose we did something with multiple spaces, like,
say, three names. Notice that it didn't actually crash this time and it did
something; it assumed that these last two names were my entire last
name and only pulled out the first name. Now, this is a violation of the
specification, but it's my fault because I've called it with an argument that
violates the precondition. And I've just discovered that by getting a
mysteriously wrong answer. Now, so looking at this, you might say, "OK;
well, what things should I enforce? What things should I not enforce?"
Well, this is really a trade-off and this is just something that you build up
with experience. You'll discover that, OK, some things you'll know how to
do and some things you won't. And you should just right now, in the
beginning, starting out, check the things that are easy and avoid the
things that aren't.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Watch: Enforcing with Helper Functions
Earlier, you saw how top-down design is used to implement the body of a
function.

In this video, Professor White shows how this approach can be used with
helper functions to fully enforce a precondition.

Video Transcript

As we've seen, enforcing preconditions can be tricky, especially when we


start to get more complex functions. Even something as simple as our
function last_name_first(), when we tried to make it a little more
interesting by allowing one or more spaces between the two names, that
created a fairly complicated precondition. And we didn't know how to
enforce it. At best, we were able to compromise. Instead of looking at
whether or not the spaces are next to each other, we just said, "OK; there
just needs to be at least one space." However, we do know how to
handle this particular issue. We've looked at this concept called top-down
design in implementing the body of a function, and we can use that for
enforcing preconditions as well. In top-down design, if we didn't know
how to do a step, we just replaced it with a function call. And then the
idea was that function was a new helper that we were going to go back to
and implement later. We can do the same thing with our assert
statements. The precondition that I want to check is whether or not the
string n is composed of two words. So I'm just going to make a function
called is_two_words(), and now I'm going to call it as part of the assert
statement.

OK; well, since I'm calling it, that means I need to have a definition, and
here we've written out the definition along with a specification. We
haven't implemented it yet — we'll come back to that in a second — but
let's just look at the specification as it is. Remember that assert
statements need to have a Boolean expression. So that is the reason
why, if I'm going to have a helper function that checks whether or not a
precondition is violated, it must be a Boolean function; It must be
something that returns "true" or "false". OK; well, you'll also notice that

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
specification has a lot more information here. That's because, all right,
actually implementing this particular function is tricky. You theoretically
know enough computer science to be able to implement this function, but
it also requires you to be clever. So we have to break up all of these
things that we actually want to be true; namely that there's no space at
the beginning or end, the space is in the middle, and if there's more than
one space the spaces are adjacent. So let's see how we would do this
function.

Well, so this is where we're checking that there's no space at the


beginning. And we need to check also that the string is not empty as well.
Here's where we're checking that there's no space at the end, and then
when we combine that with this particular condition, we're verifying that
the space must be in the middle. So notice that we're checking each one
of these, and our answer in the end is going to be all of these things
together. This very last bit here is a bit of cleverness. To make sure that
all of the strings are next to each other, what we do is we find the first
string, a space, and the last space, and then we check to make sure that
every single thing in between those two spaces is also a space. As I said,
theoretically you knew enough computer science to actually implement
this function but it did require you to be a little clever, and this is the type
of thing that you just build up with the experience. But the thing to keep in
mind is that we now actually have a fully enforced precondition. Let's try
this out. We're going to "import name2". And now we're going to call
last_name_first(). As usual, it's not going to work if I don't have any
spaces in the name at all. But also, if I've given it too many spaces, now
because the function is_two_words() works, it's going to tell me that it's
the wrong format.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Activity: Enforce with Helper Functions
In the Codio project below, navigate to the page Overview of Exercise
4. Complete all of the instructions for this exercise, checking your work as
you go. After you have completed the page Reflect on Exercise 4, you
are done. Do not move on to the next exercise until you are directed.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Tool: Fundamental Python Concepts 5

Click here to download the


Fundamental Python Concepts
Tool, if you haven't already.
Pages 6-7 cover all the content
we have seen in this module.

In this module, we explored error messages, how to read them, and how
to determine responsibility. This reference tool contains key Python
concepts we've covered in this module. Check out pages six through
seven to help you review things such as how to approach error
messages and how to enforce preconditions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Project Submission for Enforcing Specifications
You have now completed all Codio exercises for the course module
Enforcing Specifications. To submit the project, go to the page
Complete the Course Module in the Codio project and follow the
instructions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Module Wrap-up: Enforcing Specifications
In this module, you explored error messages, how to read them, and how
to determine responsibility. You also examined how to use assert
statements to help with identifying errors, how to design error messages,
and how you can enforce preconditions with helper functions.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Course Project
Programming with Functions
Read: Thank You and Farewell

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Programming with Functions
The Codio project below contains the instructions for the final course
project. Follow all instructions to complete the project and submit your
work.

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Read: Thank You and Farewell

Walker M. White
Senior Lecturer and Stephen H. Weiss Provost's Teaching Fellow
College of Computing and Information Science
Cornell University

Congratulations on completing "User-Defined Functions in Python."

I hope the material we have covered on Python functions has met


your expectations and helped you build an understanding of how to
design, test, and implement functions going forward.

From all of us at Cornell University and eCornell, thank you for


participating in this course.

Sincerely,

Walker M. White

Back to Table of Contents

© 2019 eCornell. All rights reserved. All other copyrights, trademarks, trade names, and logos are the sole property of their respective owners.
Glossary
Accumulator
An accumulator is a variable in a for-loop that stores information computed by the for-loop and will be still
available when the for-loop is complete. For example, in the code

total = 0
for x in range (5):

total = total + x

Application
An application is another name for a script.

Argument
An argument is an expression that occurs within the parentheses of a method call. The following call has two
arguments: x+y and y+w:

min(x+y, y+w)

As a general rule, the number of arguments should equal the number of parameters that are called, except
when using default arguments.

Assert statement
An assert statement is a statement in Python used to enforce an assertion. It has one of the following forms:

assert <bool>
assert <bool>, <message>

In both cases, Python evaluates the boolean expression. If the expression is True, nothing happens. If it is
False, Python will raise an exception with the (optional) provided message.

Assignment statement
An assignment statement is a statement with the following form:

<variable> = <expression>

If the variable does not yet exist, the statement creates the variable and stores the given value inside it. If the
variable does exist, then it replaces the old value with the one provided.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
1
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Attribute
An attribute is a variable belonging to either an object or a class. To reference a field, it must be preceded
either by a variable containing the object name (for an object attribute) or the class name (for a class
attribute). For example, to reference the attribute x inside of a Point object stored in the variable p,
we use p.x.

Basic type
A basic type is any type that has a corresponding literal. Basic types are fully integrated into Python (they
do not require an import statement to use) and are not mutable. In Python, the basic types are int, float,
bool, complex, and str.

Bool
A bool or boolean is a basic type whose values are True and False.

Bug
A bug is an error in a program.

Call frame
A call frame is an area of memory created whenever a function is called. The call frame contains the
following:

• The name of the function


• A program counter, which indicates the statement to execute next
• The function parameters
• Any local variables that have been created so far

The call frame changes over time as the function executes, adding, changing, or deleting variables.

Call stack
A call stack is the area of memory containing all active call frames. The call frames are arranged top to
bottom with the most recent call on the bottom of the stack. A call frame may not be erased until all call
frames below it are erased.

Cast
A cast is an operation that converts a value from one type to another. For example, the cast float(5+6)
converts the value of the expression, which is 11, to float format.

A widening cast converts from less information to more information and can be done implicitly. A
narrowing cast must be done explicitly because it may lose information or may be illegal. See narrower type
for more information.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
2
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Class
In Python 3, class and type are synonyms. However, in this course, we will use the term class type to refer to
a user-defined type that is not built into Python. The values of a class are called objects.

Command shell
The command shell is an OS-specific application that responds to text commands. On Windows, it is called
the PowerShell. On MacOS (and most Linux systems), it is called the Terminal.

Comment
A comment is a line of code that is ignored by Python and only serves as a note to the programmer. Most
comments start with a #. We sometimes refer to docstrings as comments. However, these are not actually
ignored because they are used by the help()function.

Conditional expression
A conditional expression is an expression with the following form:

<exprl> if <bool> else <expr2>

To evaluate this expression, Python first evaluates the boolean expression <bool>. If the boolean is True,
Python uses the value of <expr1> as the value of the conditional expression. Otherwise, Python uses the
value of <expr2> as the value of the conditional expression.

Conditional statement
A conditional statement is a statement of the form

if <bool>:
<statements>

Python executes this statement as follows: If the boolean expression is True, if executes the statements
underneath. Otherwise, it skips over them.

An alternate form of a conditional is as follows:

if <bool>:
<statements>

else:
<statements>

This form is executed as follows: If the boolean expression is True, it executes the statements underneath
the if. Otherwise it executes the statements underneath the else. There are additional forms of conditional
statements that use the keyword elif.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
3
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Constructor call
A constructor call is a call to a constructor function. It has the form

<classname>(<arguments>)

For example, Point (1,2,3) is a constructor call for the class Point.

A constructor call is evaluated in three steps:

1. Create an instance of class classname


2. Execute the initializer __init__(<arguments>)
3. Return the folder name of the new instance

Debugging
Debugging is the act of searching for and removing bugs in a program.

Default argument
A default argument is an argument that is provided automatically if it is omitted in the function call. Default
arguments are specified by writing the associated parameter in the form of an assignment statement. For
example, the following function header has a default argument for y:

def foo(x,y=2):

In this case, the function call foo(1) is legal; the y parameter is given the default argument 2. In this
course, we are primarily interested in object and class folder.

Dict
A dict or dictionary is a mutable type that associates keys with values. It is similar to a sequence, except that
elements are accessed by the key, not the position. For example, in the dictionary

d = { ‘alpha’:1, ‘beta’:3 }

the value d[‘alpha’] is 1, while the value d[‘beta’] is 3.

While dictionaries are not sequences, they are iterable.

Docstring
A docstring is a string literal that begins and ends with three quotation marks. Document strings are used to
write specifications and are displayed by the help() command.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
4
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Escape character
The escape character \ is used to write characters that cannot appear directly in a string. Here are a few (not
exhaustive) examples:

New-Line Character \n
Backslash Character \\
Double-Quote Character \”
Single-Quote Character \’

Evaluate
Python evaluates an expression by turning it into a value.

Float
A float is a basic type whose values are scientific numbers like 3.46E-4.

For-loop
A for-loop is a statement of the form

for <variable> in <iterable>:


<statements>

Python executes this statement as follows: It puts the first element of the iterable in the variable, and then
executes the statements indented underneath. It repeats this process as long as there are still elements
left in the iterable.

In a for-loop, the variable after the keyword for is called the loop-variable while the iterable is called the
loop-iterable or loop-sequence.

Fruitful function
A function that terminates by executing a return statement, giving an expression whose value is to be
returned. Fruitful functions (possibly) return a value other than None.

Function
A function is a set of instructions to be carried out via a function call. You can think of it like a recipe in a
cookbook. We often separate functions into fruitful functions and procedures.

Function body
The function body consists of the lines of code specifying what is to be done when the function is called. The
function body appears indented after the function header. We typically use the term function body to just
refer to the actual Python code and exclude the function specification.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
5
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Function call
A function call is an expression that executes a function body. It has the following form:

<function-name>(<arguments>)

It is important to understand how a function call is executed. Its execution is performed in five steps:

• A call frame is added to the stack with the function name.


• The arguments are evaluated and assigned to the parameters.
• The function body is executed, one line at a time.
• The call frame is erased from the stack.
• The function value is returned if the function is fruitful.

If the function is a fruitful, it returns the value of the appropriate return statement. If it is a procedure, it
returns None.

Function definition
The function definition is the code that tells Python what to do when a function is called. It consists of the
function header and function body.

Function frame
A function frame is another term for a call frame.

Function header
The function header is the part of the definition that starts with the keyword def, followed by the function
name and the parameters delimited by parentheses. The function body is immediately indented underneath.

Function name
The function name is the name used to call a function. It appears in the function header, immediately
after the keyword def.

Function specification
The function specification defines what the function does. It is used to understand how to call the function. It
must be clear, precise, and thorough, and it should mention all parameters, saying what they are used for. It
should also include a precondition for each parameter.

In Python, function specifications are written with a docstring. They appear immediately after the function
header, indented with the function body.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
6
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Function stub
A function stub is a function definition with a header and specification, but no body.

Global space
Global space is the region of memory that stores global variables.

Global variable
A global variable is a variable that is defined outside of a function or class definition. Typically, both function
names and module names are global variables; these variables each contain the identifier of the folder that
stores the function or module content in the heap.

Heap
The heap is the region of memory that stores all folders. Anything that is not a basic type must be
represented as a folder in the heap.

Immutable
The adjective immutable means “incapable of being changed.” It is typically used to refer to an attribute or
a type.

Immutable attribute
An immutable attribute is a hidden attribute that has a getter but no setter. This implies that a user is not
allowed to alter the value of this attribute. It is an important part of encapsulation.

Immutable type
An immutable type is any type that is not mutable. All of the basic types are immutable.

implementation
An implementation is a collection of Python code for a function, module, or class that satisfies a specification.
This code may be changed at any time as long as it continues to satisfy the specification.

In the case of a function, the implementation is limited to the function body. In the case of a class, the
implementation includes the bodies of all methods as well as any hidden attributes or methods. The
implementation for a module is similar to that of a class.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
7
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Import statement
An import statement is a statement with one of the following forms:

import <module> # encapsulate contents in module folder


from <module> import item # pull module element into global space

It is used to access the global variables, functions, and classes defined within a module. When we import a
module, all module variables (and function and class names) are stored in a folder. Unless the from syntax is
used, all content must be accessed using a variable with the same name as the module.

For example, if we use the statement

import math

then we write math.pi to access the variable pi in this module. On the other hand, if we write

from math import *

then all of the contents of the module math are dumped into global space. In that case, we only need to write
pi to access the same variable.

Instance
An instance of a class is an object. The terms instance and object are synonyms. We typically use the word
instance when we are referring to a collection of objects all of the same class.

Instantiate
The word instantiate means “to create an instance of.” Evaluation of a class expression C(...) instantiates
an instance of the class C.

Int
An int is a basic type whose values are integers.

Interactive shell
The interactive shell is a program that allows the user to type Python expressions and statements one at
a time, evaluating them or executing them after each step. It is not to be confused with the command shell,
which is a more general purpose tool. However, the interactive shell is often run within the command shell.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
8
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Interface
The interface is the information that another user needs to know to use a Python feature, such as a function,
module, or class. The simplest definition for this is any information displayed by the help() function.

For a function, the interface is typically the specification and the function header. For a class, the interface is
typically class specification as well as the list of all unhidden methods and their specifications. The interface
for a module is similar to that of a class.

Is
The is operator works like == except that it compares folder names, not contents. The meaning of this
operator is can never be overloaded.

Iterable
An iterable type is the type of any value that may be used in a for-loop. Examples include lists, strings,
and dictionaries.

Keyword
A keyword is a special reserved word telling Python to do something. Examples include if, for, and def.
Keywords may not be used as variable names.

List
A list is a mutable type representing a sequence of values. Lists are represented as a comma-separated
sequence of expressions inside square brackets, like this

[1, ‘Hello’, True, 3.5]

Literal
A literal is a way to represent a value in Python. Literals are used to write expressions with any of the basic
types. Here are several examples of literals:

int 354
float 3.56
complex 3.5j
bool True
str “Hello World!”
str ‘Hello World!’

Local variable
A local variable is a variable that is created within the body of a function or method.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
9
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Loop condition
The loop condition is a boolean expression in the header of a while-loop.

Loop iterable
A loop iterable is the entity that produces values for a for-loop. Because most loop iterables are sequences,
this term is often used interchangeably with a loop sequence.

Loop sequence
A loop sequence is the entity that produces values for a for-loop. The name is chosen because most of the
time for-loops use a sequence. However, the term loop iterable is more accurate.

Loop variable
A loop variable is a variable that acquires the next element of the loop iterable through each iteration of a
for-loop. In some cases, the term loop variable is extended to include any variable that appears in the loop
condition of a while-loop.

Method
A method is a function that is contained within a class definition. Methods are able to access the attributes of
the class, in addition to the parameters and local variables that any function can access.

Method call
A method call is a function call for a method. It is similar to a function call except that it has the following
form:

<folder>.<method-name>(<arguments>)

where <folder> is an object in the case of an instance method and a class in the case of a class method.

A method call creates a call frame exactly like a function call except that the folder name of <folder> is
assigned to self.

Module
A module is a file containing global variables, function definitions, class definitions, and other Python code.
The file containing the module must be the same name as the module and must end in .py. A module is used
by either importing it or running it as a script.

Module specification
A module specification is a description of the purpose of a module and how to use it. Module specifications
are typically written using docstrings.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
10
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Mutable
The adjective mutable means “capable of being changed.” It is typically used to refer to an attribute or a type.

Mutable attribute
A mutable attribute is an attribute that can be freely changed. Mutable attributes need not be encapsulated.
But if the attribute is hidden, then it will need both a getter and a setter.

Mutable type
A mutable type is a type where the values are containers and it is possible to alter the contents of the
container. An int is not mutable because it does not contain anything; it is just a number. A string is a
container of characters but it cannot be changed. The types list and dictionary are examples of mutable
types, as are most user-defined classes.

Narrower type
A narrower type is a type for which Python will perform a cast from automatically. In other words, Python will
automatically cast from a narrower type to a wider type. This concept only applies to bool and the numeric
types. The progression below shows the progression from narrowest type to the widest type:

bool -> int -> float -> complex

None
The value None actually represents the absence of a value. If Python attempts an attribute reference like
None.b or a method call like None.m(...), it will raise an exception.

Object
An object is an instance of a class. Each object for a class can access the attributes and methods defined in
and inherited by the class.

Object attribute
An object attribute is an attribute that is unique to a specific object and is therefore stored in the
object folder.

Object class
The object class is a special class built into Python named object. It is the superest class of all. Any class that
does not extend another class must extend the object class.

Object folder
The object folder is the folder in the heap that contains the attribute values unique to an object. We use this
term in place of the term object when we want to exclude any attributes or methods that might be inherited.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
11
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Object identifier
The object identifier is the folder name for an object-folder. This identifier can be accessed with the id()
function, but it is rarely useful.

Object representation
An object representation is a string that can uniquely identify an object. It is retrieved with the repr function.

Parameter
A parameter is a variable that is declared within the header of a function or method.

Pass statement
A pass statement is a statement that tells Python to do nothing. It is used to fill the body of a function stub.

Procedure
A procedure is a function that has no explicit return statement. A function call on a procedure always
evaluates to None.

Return statement
A return statement is a statement that terminates the execution of the function body in which it occurs. If it is
followed by an expression, then the function call returns the value of that expression. Otherwise, the function
call returns None.

Scope
The scope of a variable is the set of statements in which it can be referenced. For a parameter, it is the
corresponding function body. For a local variable, it is from the initial assignment statement until the variable
is deleted, or the end of the function body.

Script
A script is a Python program that is meant to be run outside of interactive mode. In particular, scripts often
have the following line of code to prevent them from being imported:

if __name__ == “__main__”:

To run a script, type python <script> in the OS command shell. When a script is run, it will execute all of the
code indented under the conditional above.

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
12
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Sequence
A sequence is a value that is an ordered list of other values. The contents of a sequence can be accessed by
bracket notation. For a sequence seq, seq[2] is the element in position 2. seq[1:5] is the slice (subsequence)
from positions 1 to 5 (including 1, but not including 5).

Examples of sequences include lists, strings, and tuples.

Statement
A statement is a command of Python to do something. Python executes a statement.

Str
A str or string is a basic type whose values are text, or sequences of characters. String literals must be either
in double or single quotes.

Test-case
A test-case is a collection of inputs, together with an expected output, used to test a single run of a program.
It is often used in unit tests to test a function.

Testing
Testing is the act of analyzing a program, looking for bugs. Unlike debugging, it does not necessarily involve
removing bugs.

Try-except statement
A try-except statement is a statement of the following form

try:
<statements>

except:
<statements>

Python attempts to execute all of the statements underneath try. If there is no error, then Python does
nothing and skips over all the statements underneath except. However, if Python crashes while inside the try
portion, it recovers and jumps over to the except. It then executes all the statements underneath there.

Tuple
A tuple is an immutable type representing a sequence of values. Tuples are represented as a
comma-separated sequence of expressions inside parentheses, like this

(1, ‘Hello’, True, 3.5)

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
13
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their
Type
A type is a set of values together with the operations on them. The type of a value can be determined via
the type() function.

Typing
Typing is a special form of precondition that requires that a variable hold a value of a specific type. It is the
most common form of function precondition in this course.

Unit test
A unit test is a collection of test cases used to test a unit of code, typically a function or method.

Variable
A variable is named with an associated value. We sometimes refer to it as a named box with the value in the
box. In Python, there are four basic kinds of variable, determined by the memory region they fit in:

• Parameters
• Local variables
• Attributes

While-loop
A while-loop is a statement with the following form:

while <bool>:
<statements>

Python executes this statement as follows: It first evaluates the boolean expression. If it is True, then it
executes the statements indented underneath. It repeats this process as long as the boolean expression is
True. This boolean expression is often referred to as the loop condition.

While-loops are difficult to use properly and are often combined with loop invariants.

Wider type
A wider type is a type for which Python will perform a cast from automatically. In other words, Python will
automatically cast from a narrower type to a wider type. This concept only applies to bool and the numeric
types. The progression below shows the progression from narrowest type to the widest type:

bool -> int -> float -> complex

© 2019 eCornell. All rights reserved. All other copyrights,


Software Development in Python
14
© 2018 eCornell. Alltrademarks,
rights reserved. Allnames,
trade other copyrights, trademarks,
and logos are the sole property
trade names, and logos are respective
the sole property
owners.of their respective owners.
Cornell Computing and Information Science of their

You might also like