Lecture 2

Objectives for today

  1. Given a function definition, identify the name, function parameters and body
  2. Distinguish between global variables, function parameters and local variables
  3. Determine the return value of a function
  4. Use built-in functions such as print, input, str, int, etc.
  5. Implement simple functions
  6. Describe aspects of good coding style

Thonny mechanics (saving files, and finding errors)

If we close and re-open Thonny and try to use variables we created last time, we will get errors

>>> x
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
NameError: name 'x' is not defined

We could repeat everything that we did, e.g.

>>> x = 4
>>> x
4

but that is very inefficient and can’t possibly be the “way”.

If we want to save our code, and use that code to regenerate results, we will need to create additional files. We can use the text editor panel in Thonny.

# An example script
x = 7
x

The line beginning with a ‘#’ is a comment and is not executed. It is a note for the reader (which will often be you).

I will create a script with our command, save the file, and then run it (with the green arrow).

The result is the similar to if we had typed the same code into the interpreter, except we don’t get line-by-line feedback. That is we are not executing each line in the context of REPL (with the “print”) but just read and eval. We will need to use explicit print functions to see the result.

# An example script
x = 7
print(x)

But we can use the variables we defined in the file later in the interpreter:

>>> x
7

Lets introduce an error so we see what Thonny reports, say by referencing an undefined variable y:

>>> %Run lecture1.py
Traceback (most recent call last):
  File "/Users/michaellinderman/Dropbox/Courses/MiddSIP-Summer2018/lectures/lecture1.py", line 3, in <module>
    print(y)
NameError: name 'y' is not defined

Functions

Last time we actually used a function str. And we just used print, another function. These are “built” into Python.

>>> str(x + 7)
'14'

What happens when you call or invoke a function like str. Python:

  1. evaluates all the arguments, the expressions within the parentheses, from left to right
  2. sets the function parameters to those values
  3. then executes the body of the function

Built in functions are just the start.

Using, and creating your own functions is a critical programming skill. Functions help us organize our code, and encapsulate computations for reuse with different inputs (think about our discussion on Day 1 about doing tasks many, many times).

Functions on Python (and other languages) are similar to their mathematical counterparts (which associates values of one set, the inputs, to another set, the outputs). For example you likely have seen this notation for functions:

Let’s recreate that functionality (and function) in Python:

>>> 3**2
9
>>> 4**2
16

I can implement the “squaring” with a function.

>>> def f(x):
    return x**2

I have now defined a function named f. Note that nothing was executed. A function definition is just that, the definition of the computation a function should perform. It does not perform the computation. To do so we will need to invoke/call the function as we did above.

Lets break down the function definition first:

Now let’s invoke out newly defined function.

>>> f(3)
9
>>> f(4)
16
>>> f(4,5)
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
TypeError: f() takes 1 positional argument but 2 were given

Why did we get an error above? We tried to call f with more parameters than specified in the function definition.

What is the current value of x?

>>> x
7

Surprised?

Note that parameters and any variables defined inside the function scope don’t change variables in the enclosing scope. That is the function definition creates a new scope. A variable’s scope is the area of a program in which its value can be accessed. A function invocation creates a new frame (which the PP book calls a “namespace”) that contains instances of the function parameters for that call and any local variables defined within the function.

This a good spot for us to pause and formalize our mental model of how memory works in Python for assignment, re-assignment and functions. Let’s use a combination of recent examples. Check out the “squaring” function in Python Tutor.

PI questions(credit)

Function writing: A recipe

  1. Write a specification of the function along some examples of the function call, e.g.

    “Return the string composed of first parameter, a space, and the second parameter”

    >>> glue_strings('John', 'Smith')
    'John Smith'
    
  2. Write the type contract, i.e. the types of parameters the function accepts and the type it returns, e.g.

    (str, str) -> str

  3. Write the header

    def glue_strings(first, second):
    
  4. Implement the body

    def glue_strings(first, second):
        return first + " " + second
    

Here is a more complex example for compound interest. Recall we can compute the value of savings yearly interest as

where P is the principal, r is the rate and t is the time in years. How could we implement this in Python? (Show code.)

# Calculates investment value with interest compounded yearly
def compound_interest(prinicpal, rate, years):
	amount = principal * ((1 + rate) ** years)
	return amount

Note the function body can be multiple lines. The comment (beginning with #) is used to document to myself what the function does. Here we also define a “local variable” amount. Much like parameters, local variables exist only within the scope of the function body (local variables are stored in frame).

And even some more function examples.

So far we have largely glossed over the difference between print and return. Consider:

>>> def double_it(x):
...     return x * 2
...
>>> def another_double_it(x):
...     print(x * 2)
...

when run in the interpreter, both functions produce the same output

>>> double_it(12)
24
>>> another_double_it(12)
24

but they are not the same. What if instead we assign the result to a variable?

>>> y = double_it(12)
>>> print(y)
24
>>> y = another_double_it(12)
24
>>> print(y)
None

Without the return statement the value returned by the function to the caller is the special constant None which we briefly discussed previously.

Remember that when you type an expression into the console (i.e., at the “»>” prompt), the interpreter evaluates the expression and prints the result. The interpreter is effectively wrapping each expression with an implicit print; that is not the case when running a script.

Most of the time you will be using return, because most of your functions will intended for use as part of larger scripts or applications that will consume the value produced by the function. And you don’t wan’t random values printing all the time…

Good coding practice

A little about style

We should always aim for well-chosen, that is self-documenting, names for functions, variables, etc., and informative (but pithy) comments. The quality of your coding style will also be a factor in your grade. Code should be designed to be read not just executed!

We will discuss style a lot more as we go, but it is never to early to develop good habits (and unlearn bad ones). In general we will follow the PEP 8 style guide.

Comments

In your lab we asked you to comment your code. The purpose of the comments is to describe what your code is doing, not to describe the code itself; you can assume the reader knows Python. But the reader doesn’t necessarily know what you are trying to do.

We will generally encounter two kinds of comments:

  1. Block and inline comments
  2. Docstrings

The former is what we have used so far. Comments, starting with a #, included in the code itself. Everything after the # on the line will not be executed by Python. The second is a structured mechanism for documenting function, classes and other entities in your Python code.

Here is an example docstring for a function, a bad example:

def foo(x):
    """
    this is my docstring
    it can take up multiple lines
    this is a horrible docstring because it doesn't describe how the function behaves
    """
    return x * 2

Docstrings are a formal part of the Python specification. We will use the three double quotes format, i.e., """ (docstrings are a special kind of multi-line comment). If the function is very simple we can use a single line. If not, we want to document the purpose, parameters, return value and any exceptions.

def foo(x):
    """
    Doubles input.
    
    Args:
    	x: value to be doubled
    
    Returns:
    	Doubled value
    """
    return x * 2

What is the difference between docstrings and inline comments? The intended audience.

A docstring is intended for those who want to use your function, so it should describe what the function does, its parameters, and what (if any) return value is produces.

An inline comment is intended for those who want to understand what you were trying to do with your code.

The docstring informs the output the help functionality. As you saw in the reading you can obtain the documentation for functions, etc. with help. For example:

>>> help(print)
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.

or

def double_it(x):
    """Double the input"""
    return 2*x

>>> help(double_it)
Help on function double_it in module __main__:

double_it(x)
    Double the input

Being DRY

You will often hear the acronym ‘DRY’ for Don’t Repeat Yourself, often as used as a verb. For example, “DRY it up”. The goal is eliminate duplicated code. Why? Duplicated code makes it harder to read and to update your code (to correct a mistake you might have to change a computation in many places!). Often the way to DRY it up is encapsulate duplicated code as a function!

Summary

  1. Functions
  2. Print vs. Return
  3. Good coding practice