10. Functions, I: Read help docs and use

There is a huge amount of functionality in the Python universe. We want to understand how each item is intended to be used, to know what options exist, and hopefully to find example cases that demonstrate appropriate syntax. This is true for both built-in functions and for those in the zoo of modules that we can import. Fortunately, most objects in Python have a help description or docstring (= "documentation string"). And later, when we write our own functions, we will want to write our own help content, too.

There are multiple syntaxes for checking the help of items, some depending on the module being explored. We will demonstrate some using both built-in functions and those from the math module. So, let's start by importing the latter so we have it available:

import math

When reading help files, we will focus a lot on understanding functions and functionality associated with objects.

10.1. Thinking about functions: input(s) and output(s)

When looking to use a function, we definitely want to know what the output(s) will be, which is what the function returns. There can be zero, one or more returned items. For example, sometimes a function will just print a message and no value is output; or we might calculate the area of a rectangle and return that; other times, we might output a set of values like distance, velocity and acceleration. Occasionally, we might just change one of the inputs instead of returning something else (called operating in-place).

The inputs to a function in computing are typically called arguments (and often abbreviated as args) or parameters. As with outputs, there can be zero, one or more arguments when using (or calling) a function. But inputs have an added layer of consideration because functions can have a combination of required and optional arguments. The optional arguments have default values (i.e., a parameter is set to have a particular value unless we specify otherwise), and there are a few categories of optional arguments, which have different syntaxes.

We can have a bit of intuition about this variety and flexibility of input usage from the mathematical context. Consider the example of the Gaussian function in mathematics, which is often written as:

(1)G(x) = \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{1}{2}\frac{(x-\mu)^2}{\sigma^2}}

While this is a function of one variable x, to calculate an explicit value we also need to know the values of the parameters \mu and \sigma. To reflect this fact, sometimes in mathematics the LHS is written as G(x; \mu, \sigma), which balances displaying the "full" number of inputs for a calculation with differentiating the kinds of inputs (the actual variable x from the parameters \mu and \sigma) with a semi-colon ";". Python will not use a semi-colon to separate inputs, but it is a useful concept to keep in mind.

Furthermore, in many applications (such as statistics) one particular case of the Gaussian is overwhelmingly used. It is called the Normal distribution, which occurs when the parameters \mu=0 and \sigma=1. So, while recognizing that someone talking about a Gaussian has the freedom to choose any values for \mu and \sigma that they want, we might picture a Gaussian as having those Normal parameter values, unless otherwise specified (i.e., by default).

We will find that many functions in Python work in an analogous way: some argument(s) might be required, and some might be optional (while having default values). We will now investigate how all of this is specified in the way help files are presented.

10.2. Help doc examples

  1. Let's look at some help files starting with math module's cosine function. One way to view its help is to put a question mark ? after it:

    math.cos?
    

    ... which produces:

    1Signature: math.cos(x, /)
    2Docstring: Return the cosine of x (measured in radians).
    3Type:      builtin_function_or_method
    

    Line 1 shows the possible arguments (the signature of the function), which are separated by commas. In this case, there is actually just one input, represented by x, and the / that looks like an odd second argument is actually a relatively new Python syntax that shows the "end of required arguments"; its usefulness would be more apparent if there were more kinds of arguments present, and it is not included when executing the function. In Line 2, we see the docstring and learn that the output (what is returned by the function) is the evaluation of \cos(x), and importantly that the units of x are radians (they could also easily have been degrees, so this clarification is vital). Line 3 tells us the type of the object we are looking at---we mentioned before that all objects in Python have a type, and even functions like this do!

    Thus, we can probably guess what the value of:

    math.cos(3.14159)
    

    should be close to. We now also know that if we entered math.cos(180) hoping for the input to be interpreted as degrees, then we would become sad---or worse, we might not realize our mistaken understanding/usage of the function, and then we would have incorrect results lurking in our code. And if we tried to calculate the cosine of 3 separate values here with math.cos(1, 2, 3), then we should not be surprised to see an error:

    1---------------------------------------------------------------------------
    2TypeError                                 Traceback (most recent call last)
    3<ipython-input-103-b0fec430d18e> in <module>
    4----> 1 math.cos(1, 2, 3)
    5
    6TypeError: cos() takes exactly one argument (3 given)
    

    ... politely reminding us in Line 6 about the correct number of arguments, as opposed to what we entered (recall: arguments are always separated by commas).

  2. Let's look at another function in math:

    math.pow?
    

    What does this do?

    1Signature: math.pow(x, y, /)
    2Docstring: Return x**y (x to the power of y).
    3Type:      builtin_function_or_method
    

    In Line 1, we see that this requires 2 arguments to be used (again, the / symbol is not an input and just helps classify the args). In Line 2, we see what the output is (what is returned), and how each of x and y are used.

    When we try to use this function and provide two values, such as with math.pow(3, 4), how does Python know which value to provide to which argument? Well, the required arguments are here are called position-only arguments, meaning that our input values will just be mapped in the order they appear: we need to input 2 values, and the first will be given to x and the second to y. There is no alternative here, such as writing writing math.pow(y=4, x=3): that would produce an error. (Note that the single argument for math.cos, above, is also a position-only arg.)

    You can check that math.pow(3, 4) and math.pow(4, 3) provide the expected results for 3^4 and 4^3, respectively.

  3. Let's look at the help of Python's built-in print() function:

    print?
    

    ... which produces:

     1Docstring:
     2print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
     3
     4Prints the values to a stream, or to sys.stdout by default.
     5Optional keyword arguments:
     6file:  a file-like object (stream); defaults to the current sys.stdout.
     7sep:   string inserted between values, default a space.
     8end:   string appended after the last value, default a newline.
     9flush: whether to forcibly flush the stream.
    10Type:      builtin_function_or_method
    

    Line 2 shows some new syntax in the argument list. Firstly, there is an input value, and then the ellipsis ...: recall from our earlier examples of using print(), that we can enter any number of comma-separated items to be displayed in the sequence they are entered. So, that part of the syntax means that we can have any number of position-only arguments as inputs.

    What about the new syntax with the = for the other arguments, such as sep=' '? Well, these are called keyword arguments, often abbreviated as the fun-to-say kwargs. They have default values (sep's is an empty space str, ' '), and the use of each is described in the help as well (sep's is provided in Line 7). To alter the values of these parameters, we have to explicitly identify the kwarg by name when using the function.

    As an example, see the difference between the two lines output by:

    print('apple', 'banana', 'ice cream')
    print('apple', 'banana', 'ice cream', sep='---')
    

    ... which are:

    apple banana ice cream
    apple---banana---ice cream
    

    (As a sidenote, we can now understand why there were spaces appearing in our output lists of printed items: it was a parameter default in the function.)

    Note that in order to specify new values for these kwargs, we must use the keyword explicitly; their usage is not position-based. Thus, note that trying to reproduce the second print line with:

    print('apple', 'banana', 'ice cream', '---')
    

    would not be successful. It would not produce an Error in this specific case, but the interpreter would just treat the last argument as another "value" item to display in sequence:

    apple banana ice cream ---
    

    Since there can be any number input values to display in sequence, it makes sense that the other arguments would require a keyword to identify them specifically.

    However, below we will see examples where arguments can be both keyword- and position-specified.

    Note

    We might notice that the / symbol is not present in the argument list here. Well, sometimes it is and sometimes it isn't, across all possible Python functions. C'est la vie, we guess. It's OK by us to not have it, and we should mostly rely on recognizing position-only args and kwargs without it.

  4. How about if we wanted to find out about calculating logarithms? Asking for the help:

    math.log?
    

    ... produces:

    1Docstring:
    2log(x, [base=math.e])
    3Return the logarithm of x to the given base.
    4
    5If the base not specified, returns the natural logarithm (base e) of x.
    6Type:      builtin_function_or_method
    

    In the function usage in Line 2, we see some interesting syntax. We should now recognize that the first argument is a position-only arg and one that is required. The second one looks like a keyword argument, but why does it have square brackets around it? In fact, the syntax signifies that the second argument is optional and has a default value... but the square-brackets surrounding it highlight that it's not a normal keyword argument can be specified as base=VALUE. Instead, it would be specified only by position. So we could use math.log(100, 10), but not math.log(100, base=10).

    The reason for having a second argument at all relates to the mathematical Gaussian example above. When asking for the log of a number, we actually need more information: namely, what base the calculation is using (recall: if x = b^p, then \log_{b}(x) = p, where b is the base). So, the base is effectively an extra parameter we need to know, but there are very common bases that people often use, such as e (for natural logarithms) and 10. The syntax of this help file and Line 5 tells us that base's default value here is the number e (Euler's number, approx. 2.7182818), but we could specify another base if we wanted.

    Thus, math.log(100) evaluates to 4.605170185988092, while:

    math.log(100, 10)
    

    evaluates to 2.0.

    Comparing this function's argument syntax with that of the print() function, we can see why this function's kwarg could also be specified by position here, but was not possible in the former. Consider that math.log() takes a single, required argument, and at most one more argument: so, whatever is specified second (if at all) could only be the base parameter. The print() function can both take any number of initial values and there are several additional parameters that can be entered: there is no way to distinguish what parameter is being re-defined (if any) purely by position.

  5. As a final quick example, we note that we could check the help for non-functional things, too, such as the math.e we came across above:

    math.e?
    

    ... produces:

    1Type:        float
    2String form: 2.718281828459045
    3Docstring:   Convert a string or number to a floating point number, if possible.
    

    This object is type float, and indeed does appear to be the Euler number. Note that we would not use this features as math.e(), because it is not a function, but instead more like a variable name within the module math.e.

Q: Look at the help information on the built-in function round(). How many arguments does it take, which are required, and which are optional? Let num = 1234.56789, and how could we use round to display num rounded to the hundredths digit? How about to the hundreds digit (read the help file carefully for this, and maybe test different optional values...)

+ show/hide response

Q: Is the output type in each of the uses of round() in the previous question surprising, or expected? Why?

+ show/hide response

10.3. Other ways to check documentation/help

The ?-based syntax for seeing help documentation is convenient (likely math.cos?, above). There is also a built-in function in Python called help() that can be useful. In many cases, it provides nearly the same documentation information as using ?, but occasionally it is different. Additionally, if one is using IPython, the help() provides a scrollable version of the help doc (sometimes different than what is shown with the ?) for modules and functions, within which one can perform string searches.

For example, after typing:

help(math)
 1Help on module math:
 2
 3NAME
 4    math
 5
 6MODULE REFERENCE
 7    https://docs.python.org/3.7/library/math
 8
 9    The following documentation is automatically generated from the Python
10    source files.  It may be incomplete, incorrect or include features that
11    are considered implementation detail and may vary between Python
12    implementations.  When in doubt, consult the module reference at the
13    location listed above.
14
15DESCRIPTION
16    This module provides access to the mathematical functions
17    defined by the C standard.
18
19FUNCTIONS
20    acos(x, /)
21        Return the arc cosine (measured in radians) of x.
22
23    acosh(x, /)
24        Return the inverse hyperbolic cosine of x.
25
26    asin(x, /)
27        Return the arc sine (measured in radians) of x.
28
29    asinh(x, /)
30        Return the inverse hyperbolic sine of x.
31
32...

which continues on.

However, in many cases, there isn't much difference between using help() and ?. The output of help(math.log) is:

1Help on built-in function log in module math:
2
3log(...)
4    log(x, [base=math.e])
5    Return the logarithm of x to the given base.
6
7    If the base not specified, returns the natural logarithm (base e) of x.

... which can be compared to math.log?, above.

Finally, sometimes help() will show unexpected documentation. For example, the output of help(math.e) will display information about float objects in general, not about the specific object and its value. It isn't totally random, because we did see above that math.e is itself a float, but it still might not be what we prefer. So, you can always see whether using help() or ? provides the expected results.

For contents of the numpy module specifically, there is a function to display help doc strings. It is np.info(), and its usage is, for example:

np.info(np.log)

It also accepts non-numpy objects as inputs, e.g., np.info(print) and np.info(math.log).

10.4. Keyboard shortcuts (esp. for IPython)

In a Jupyter-notebook, one can just interact with the documentation using webpage features.

But in some other environments, like Ipython or the plain python one, one might want (or need) to navigate with keyboard presses, to view, search and even exit the help documentation. We briefly list some keyboard features for interacting with help files in these environments.

  • up/down arrow keys (or mouse scroll wheel): scroll up or down the help page

  • Spacebar: scroll down a whole page

  • /: prepare to start a search for text within the whole help page (the prompt will change from : to /)

    • then type the string to search for (for example: log)

    • then hit Enter to start a "downward" search. If instances exist within/below the current view panel, each will be highlighted and the first will be jumped to.

    • n: navigate to the next (downward) found item

  • q: typing this at the : prompt will quit viewing the current documentation.

10.5. Practice

  1. What is the difference between an arg and a kwarg?

  2. How many values must the user input when using each of the following functions? How many keyword-arguments are in each?

    math.atan()
    math.atan2()
    math.log()
    np.log()
    random.random()
    random.gauss()
    os.chdir()
    
  3. How many different log()-type functions does Python's math module contain? How is each different?

  4. Read the help doc of the NumPy module's zeros() function. How do you make an array of 5 zeros, where each is of datatype int?

  5. Import the random module using import random. Use the randint() function in that module to generate a random int in the range [-5, 5). Execute it several times to verify that the range seems appropriate.