Writing functions¶

Objective¶

  • Explain and identify the difference between function definition and function call.
  • Write a function that takes a small, fixed number of arguments and produces a single result.

Define a function using def with a name, parameters, and a block of code.¶

  • Begin the definition of a new function with def.
  • Followed by the name of the function.
    • Must obey the same rules as variable names.
  • Then parameters in parentheses.
    • Empty parentheses if the function doesn't take any inputs.
    • We will discuss this in detail in a moment.
  • Then a colon.
  • Then an indented block of code.
In [1]:
def print_greeting():
    print('Hello!')
    print('The weather is nice today.')
    print('Right?')

Defining a function does not run it.¶

  • Defining a function does not run it.
    • Like assigning a value to a variable.
  • Must call the function to execute the code it contains.
In [2]:
print_greeting()
Hello!
The weather is nice today.
Right?

Arguments in a function call are matched to its defined parameters.¶

  • Functions are most useful when they can operate on different data.
  • Specify parameters when defining a function.
    • These become variables when the function is executed.
    • Are assigned the arguments in the call (i.e., the values passed to the function).
    • If you don't name the arguments when using them in the call, the arguments will be matched to parameters in the order the parameters are defined in the function.
In [3]:
def print_date(year, month, day):
    joined = str(year) + '/' + str(month) + '/' + str(day)
    print(joined)

print_date(1871, 3, 19)
1871/3/19
In [4]:
print_date(month=3, day=19, year=1871)
1871/3/19

Functions may return a result to their caller using return.¶

  • Use return ... to give a value back to the caller.
  • May occur anywhere in the function.
  • But functions are easier to understand if return occurs:
    • At the start to handle special cases.
    • At the very end, with a final result.
In [7]:
def average(values):
    if len(values) == 0:
        return None
    return sum(values) / len(values)

a = average([1, 3, 4])
print('average of actual values:', a)
print('average of empty list:', average([]))
average of actual values: 2.6666666666666665
average of empty list: None

Exercise¶

  1. Follow these instructions:
    1. Read the code below and try to identify what the errors are without running it.
    2. Run the code and read the error message. Is it a SyntaxError or an IndentationError?
    3. Fix the error.
    4. Repeat steps 2 and 3 until you have fixed all the errors.
def another_function
  print("Syntax errors are annoying.")
   print("But at least python tells us about them!")
  print("So they are usually not too hard to fix.")
  1. What does the following program print?
def report(pressure):
    print('pressure is', pressure)

print('calling', report, 22.5)
  1. Assume that the following code has been executed:
import pandas as pd

data_asia = pd.read_csv('data/gapminder_gdp_asia.csv', index_col=0)
japan = data_asia.loc['Japan']
1. Complete the statements below to obtain the average GDP for Japan
  across the years reported for the 1980s.
  

```python year = 1983 gdp_decade = 'gdpPercap_' + str(year // ____) avg = (japan.loc[gdp_decade + ___] + japan.loc[gdp_decade + ___]) / 2

  
  2. Abstract the code above into a single function.
    
```python
def avg_gdp_in_decade(country, continent, year):
    data_countries = pd.read_csv('data/gapminder_gdp_'+___+'.csv',delimiter=',',index_col=0)
    ____
    ____
    ____
    return avg

Exercise with solution¶

In mathematics, a dynamical system is a system in which a function describes the time dependence of a point in a geometrical space. A canonical example of a dynamical system is the logistic map, a growth model that computes a new population density (between 0 and 1) based on the current density. In the model, time takes discrete values 0, 1, 2, ...

  1. Define a function called logistic_map that takes two inputs: x, representing the current population (at time t), and a parameter r = 1. This function should return a value representing the state of the system (population) at time t + 1, using the mapping function:

f(t+1) = r * f(t) * [1 - f(t)]

  1. Using a for or while loop, iterate the logistic_map function defined in part 1, starting from an initial population of 0.5, for a period of time t_final = 10. Store the intermediate results in a list so that after the loop terminates you have accumulated a sequence of values representing the state of the logistic map at times t = [0,1,...,t_final] (11 values in total). Print this list to see the evolution of the population.

  2. Encapsulate the logic of your loop into a function called iterate that takes the initial population as its first input, the parameter t_final as its second input and the parameter r as its third input. The function should return the list of values representing the state of the logistic map at times t = [0,1,...,t_final]. Run this function for periods t_final = 100 and 1000 and print some of the values. Is the population trending toward a steady state?

def logistic_map(x, r):
    return r * x * (1 - x)
initial_population = 0.5
t_final = 10
r = 1.0
population = [initial_population]

for t in range(t_final):
    population.append( logistic_map(population[t], r) )
def iterate(initial_population, t_final, r):
    population = [initial_population]
    for t in range(t_final):
        population.append( logistic_map(population[t], r) )
    return population

for period in (10, 100, 1000):
    population = iterate(0.5, period, 1)
    print(population[-1])

Takeaway¶

  • Break programs down into functions to make them easier to understand.
  • Define a function using def with a name, parameters, and a block of code.
  • Defining a function does not run it.
  • Arguments in a function call are matched to its defined parameters.
  • Functions may return a result to their caller using return.

Scope¶

Objectives¶

  • Identify local and global variables.
  • Identify parameters as local variables.
  • Read a traceback and determine the file, function, and line number on which the error occurred, the type of error, and the error message.

The scope of a variable is the part of a program that can 'see' that variable.¶

  • There are only so many sensible names for variables.
  • People using functions shouldn't have to worry about what variable names the author of the function used.
  • People writing functions shouldn't have to worry about what variable names the function's caller uses.
  • The part of a program in which a variable is visible is called its scope.
In [11]:
pressure = 103.9

def adjust(t):
    temperature = t * 1.43 / pressure
    return temperature
  • pressure is a global variable.
    • Defined outside any particular function.
    • Visible everywhere.
  • t and temperature are local variables in adjust.
    • Defined in the function.
    • Not visible in the main program.
    • Remember: a function parameter is a variable that is automatically assigned a value when the function is called.
In [12]:
print('adjusted:', adjust(0.9))
print('temperature after call:', temperature)
adjusted: 0.01238691049085659
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[12], line 2
      1 print('adjusted:', adjust(0.9))
----> 2 print('temperature after call:', temperature)

NameError: name 'temperature' is not defined

Exercise¶

Reading Error Messages¶

Read the traceback below, and identify the following:

  1. How many levels does the traceback have?
  2. What is the file name where the error occurred?
  3. What is the function name where the error occurred?
  4. On which line number in this function did the error occur?
  5. What is the type of error?
  6. What is the error message?
error
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-2-e4c4cbafeeb5> in <module>()
      1 import errors_02
----> 2 errors_02.print_friday_message()

/Users/ghopper/thesis/code/errors_02.py in print_friday_message()
     13
     14 def print_friday_message():
---> 15     print_message("Friday")

/Users/ghopper/thesis/code/errors_02.py in print_message(day)
      9         "sunday": "Aw, the weekend is almost over."
     10     }
---> 11     print(messages[day])
     12
     13

KeyError: 'Friday'

Program Style¶

Objectives¶

  • Provide sound justifications for basic rules of coding style.
  • Refactor one-page programs to make them more readable and justify the changes.
  • Use Python community coding standards (PEP-8).

Coding style¶

A consistent coding style helps others (including our future selves) read and understand code more easily. Code is read much more often than it is written, and as the Zen of Python states, "Readability counts". Python proposed a standard style through one of its first Python Enhancement Proposals (PEP), PEP8.

Some points worth highlighting:

  • document your code and ensure that assumptions, internal algorithms, expected inputs, expected outputs, etc., are clear
  • use clear, semantically meaningful variable names
  • use white-space, not tabs, to indent lines (tabs can cause problems across different text editors, operating systems, and version control systems)

Follow standard Python style in your code.¶

  • PEP8: a style guide for Python that discusses topics such as how to name variables, how to indent your code, how to structure your import statements, etc. Adhering to PEP8 makes it easier for other Python developers to read and understand your code, and to understand what their contributions should look like.
  • To check your code for compliance with PEP8, you can use the pycodestyle application and tools like the black code formatter can automatically format your code to conform to PEP8 and pycodestyle (a Jupyter notebook formatter also exists nb_black).
  • Some groups and organizations follow different style guidelines besides PEP8. For example, the Google style guide on Python makes slightly different recommendations. Google wrote an application that can help you format your code in either their style or PEP8 called yapf.
  • With respect to coding style, the key is consistency. Choose a style for your project be it PEP8, the Google style, or something else and do your best to ensure that you and anyone else you are collaborating with sticks to it. Consistency within a project is often more impactful than the particular style used. A consistent style will make your software easier to read and understand for others and for your future self.

Use assertions to check for internal errors.¶

Assertions are a simple but powerful method for making sure that the context in which your code is executing is as you expect.

def calc_bulk_density(mass, volume):
    '''Return dry bulk density = powder mass / powder volume.'''
    assert volume > 0
    return mass / volume

If the assertion is False, the Python interpreter raises an AssertionError runtime exception. The source code for the expression that failed will be displayed as part of the error message. To ignore assertions in your code run the interpreter with the '-O' (optimize) switch. Assertions should contain only simple checks and never change the state of the program. For example, an assertion should never contain an assignment.

Use docstrings to provide builtin help.¶

If the first thing in a function is a character string that is not assigned directly to a variable, Python attaches it to the function, accessible via the builtin help function. This string that provides documentation is also known as a docstring.

def average(values):
    "Return average of values, or None if no values are supplied."

    if len(values) == 0:
        return None
    return sum(values) / len(values)

help(average)
Help on function average in module __main__:

average(values)
    Return average of values, or None if no values are supplied.

Multiline Strings¶

Often use multiline strings for documentation. These start and end with three quote characters (either single or double) and end with three matching characters.

"""This string spans
multiple lines.

Blank lines are allowed."""

Exercise¶

  1. Clean Up This Code
    1. Read this short program and try to predict what it does.
    2. Run it: how accurate was your prediction?
    3. Refactor the program to make it more readable. Remember to run it after each change to ensure its behavior hasn't changed.
    4. Compare your rewrite with your neighbor's. What did you do the same? What did you do differently, and why?
n = 10
s = 'et cetera'
print(s)
i = 0
while i < n:
    # print('at', j)
    new = ''
    for j in range(len(s)):
        left = j-1
        right = (j+1)%len(s)
        if s[left]==s[right]: new = new + '-'
        else: new = new + '*'
    s=''.join(new)
    print(s)
    i += 1

Takeaway¶

  • Follow standard Python style in your code.
  • Use docstrings to provide builtin help.

Python supports a large and diverse community across academia and industry.¶

  • The Python 3 documentation covers the core language and the standard library.

  • PyCon is the largest annual conference for the Python community.

  • SciPy is a rich collection of scientific utilities. It is also the name of a series of annual conferences.

  • Jupyter is the home of Project Jupyter.

  • Pandas is the home of the Pandas data library.

  • Stack Overflow's general Python section can be helpful, as well as the sections on NumPy, SciPy, and Pandas.

End¶

  1. Lists
  2. Loops
  3. Functions