Documentation#
As we have probably all heard before, good documentation is almost as important (if not equally as important) as good code itself. You may have written some elegant and powerful code to solve your problems today, but weeks or months from now, that code may become functionally useless if you forget what it does or how to call it. Python3 users have a special built-in tool at their disposal called docstrings that make documenting functions easy. After going through this module, students should be able to:
Write well-crafted docstrings for all functions
Add type hints to function definitions
Docstrings#
Docstrings are special strings that appear immediately following function definitions in our code. They should be surrounded by three double-quotation marks on each side, and they may span multiple lines. For example:
def a_function():
"""
This is a docstring.
"""
# code goes here
return
The above is a valid docstring, but it is not a very helpful docstring. When you write docstrings, at a minimum try to include the following sections:
A short description of the purpose of the function
A list of arguments, including type
A list of returned values, including type
A better template for a docstring (based on the Google Style Guide) might look like:
def a_function(arg1, arg2):
"""
This function does XYZ.
Args:
arg1 (type): Define what is expected for arg1.
arg2 (type): Define what is expected for arg2.
Returns:
result (type): Define what is expected for result.
"""
# code goes here
return(result)
The description should be succinct, yet complete. Arguments should be listed by name and the expected type (e.g., bool, float, str, etc) should be stated. And the return result(s) should be listed along with the expected type(s).
Let’s look at one more example using a real function:
def add_and_square(num1, num2):
"""
Given two numbers, this function will first add them together, then square the sum
and return the result.
Args:
num1 (float): The first number.
num2 (float): The second number.
Returns:
result (float): The square of the sum of input arguments.
"""
result = (num1+num2)**2
return(result)
Note
Notice above we are using more-or-less complete sentences with proper grammar.
Next, let’s add docstrings to our groceries.py code we have been
working on:
1#!/usr/bin/env python3
2import json
3
4def compute_average_quantity(a_list_of_dicts, a_key_string):
5 """
6 Iterate through a list of dictionaries, pulling out values associated with
7 a given key. Returns the average of those values.
8
9 Args:
10 a_list_of_dicts (list): A list of dictionaries, each dict should have the
11 same set of keys.
12 a_key_string (string): A key that appears in each dictionary associated
13 with the desired value( will enforce float type).
14
15 Returns:
16 result (float): Average value.
17 """
18
19 total_quantity = 0
20 for item in a_list_of_dicts:
21 total_quantity += item[ a_key_string ]
22
23 return float( total_quantity / len( a_list_of_dicts ) )
24
25def calc_total_price( price, quantity ):
26 """
27 ???
28 """
29
30 total_price = price * quantity
31 return total_price
32
33def count_categories(a_list_of_dicts, a_key_string):
34 """
35 Given a list of dictionaries and a key, count the number of times each category
36 appears in the list of dictionaries.
37
38 Args:
39 a_list_of_dicts (list): A list of dictionaries, each dict should have the
40 same set of keys.
41 a_key_string (string): A key that appears in each dictionary associated
42 with the desired value.
43
44 Returns:
45 categories_observed (dict): A dictionary where the keys are the categories
46 observed in the list of dictionaries and the values
47 are the number of times that category appears
48 """
49 categories_observed = {}
50 for item in a_list_of_dicts:
51 if item[a_key_string] in categories_observed:
52 categories_observed[item[a_key_string]] += 1
53 else:
54 categories_observed[item[a_key_string]] = 1
55 return(categories_observed)
56
57def main():
58 with open('groceries.json', 'r') as f:
59 grocery_data = json.load( f )
60
61 print( compute_average_quantity( grocery_data['items'], 'quantity' ) )
62
63 for row in grocery_data['items']:
64 total_price = calc_total_price( float( row['price']), float( row['quantity'] ) )
65 print(f'Total Price: {total_price:.2f}')
66
67 print( count_categories( grocery_data['items'], 'category' ) )
68
69if __name__ == '__main__':
70 main()
In general, your main() function usually does not need a docstring. It is
good habit to write the main() function simply and clearly enough that it is
self explanatory, with perhaps a few comments to help. If you do add a docstring
to the main() function, you may write a few short summary sentences but omit
the Args and Returns sections.
EXERCISE#
Write the missing docstring for the calc_total_price() function above.
EXERCISE#
Open up the Python3 interactive interpreter. Import your groceries.py
methods. Use the commands dir() and help() to find and read the docstrings
that you wrote.
Type Hints#
Type hints in function definitions indicate what types are expected as input and output of a function. No checking actually happens at runtime, so if you send the wrong type of data as an argument, the type hint itself won’t cause it to return an error. Think of type hints simply as documentation or annotations to help the reader understand how to use a function.
Warning
In the code blocks below, we omit docstrings for brevity only. Please keep including docstrings in your code.
Type hints should take form:
def a_function(arg_name: arg_type) -> return_type:
# code goes here
return(result)
In the above example, we are providing a single argument called arg_name that
should be of type arg_type. The expected return value should be return_type.
Let’s look at an example using a real function:
def add_and_square(num1: float, num2: float) -> float:
result = (num1+num2)**2
return(result)
Next, add type hints to the function definitions of the groceries.py
script (only showing snippets below):
def compute_average_quantity(a_list_of_dicts: list[dict], a_key_string: str) -> float:
def calc_total_price(price: float, quantity: float) -> float:
def count_categories(a_list_of_dicts, a_key_string): # what about this one?
Although Python3 does not check or enforce types at run time, there are other tools that make use of type hints to check types at the time of development. For example, some IDEs (including PyCharm) will evaluate type hints as you write code and provide an alert if you call a function in a way other than what the type hint suggests. In addition, there are Python3 libraries like mypy that can wrap your Python3 programs and check / evaluate type hints as you go, provided errors where types don’t match.
Warning
Be aware that there is some redundancy in the information contained in type hints and in the docstrings. Be careful not to let them get out of sync as your code evolves.