6. Using Functions

6.1. Motivation

How to reuse code so we can write less?

When we write a loop, the code is executed multiple times, once for each iteration.

This is a simple form of code reuse that

  • gives your code an elegant structure that

  • can be executed efficiently by a computer, and

  • interpreted easily by a programmer.

How to repeat execution at different times, in different programs, and in slightly different ways?

6.2. Functions

How to calculate the logarithm?

There is no arithmetic operator for logarithm.
Do we have to implement it ourselves?

We can use the function log from the math module:

from math import log
log(256, 2)  # log base 2 of 256
8.0

The above computes the base-\(2\) logarithm, \(\log_2(256)\). Like functions in mathematics, a computer function log

  • is called/invoked with some input arguments (256, 2) following the function, and

  • returns an output value computed from the input arguments.

# A function is callable while an integer is not
callable(log), callable(1)
(True, False)

Unlike mathematical functions:

  • A computer function may require no arguments, but we still need to call it with ().

input()
  • A computer function may have side effects and return None.

x = print()
print(x, 'of type', type(x))
None of type <class 'NoneType'>

An argument of a function call can be any expression.

print('1st input:', input(), '2nd input', input())

Note also that

  • the argument can also be a function call like function composition in mathematics.

  • Before a function call is executed, its arguments are evaluated first from left to right.

Why not implement logarithm yourself?

  • The function from standard library is efficiently implemented and thoroughly tested/documented.

  • Knowing what a function does is often insufficient for an efficient implementation.
    (See how to calculate logarithm as an example.)

Indeed, the math library does not implement log itself:

CPython implementation detail: The math module consists mostly of thin wrappers around the platform C math library functions. - pydoc last paragraph

(See the source code wrapper for log.)

Exercise What is a function in programming?

  • A function is a structure that allows a piece of code to be reused in a program.

  • A function can adapt its computations to different situations using input arguments.

6.3. Import Functions from Modules

How to import functions?

We can use the import statement to import multiple functions into the program global frame.

%%mytutor -h 300
from math import log10, ceil
x = 1234
print('Number of digits of x:', ceil(log10(x)))

The above import both the functions log10 and ceil from math to compute the number \(\lceil \log_{10}(x)\rceil\) of digits of a strictly positive integer \(x\).

How to import all functions from a library?

%%mytutor -h 300
from math import *  # import all except names starting with an underscore
print('{:.2f}, {:.2f}, {:.2f}'.format(sin(pi / 6), cos(pi / 3), tan(pi / 4)))

The above uses the wildcard * to import (nearly) all the functions/variables provided in math.

What if different packages define the same function?

%%mytutor -h 300
print('{}'.format(pow(-1, 2)))
print('{:.2f}'.format(pow(-1, 1 / 2)))
from math import *
print('{}'.format(pow(-1, 2)))
print('{:.2f}'.format(pow(-1, 1 / 2)))
  • The function pow imported from math overwrites the built-in function pow.

  • Unlike the built-in function, pow from math returns only floats but not integers nor complex numbers.

  • We say that the import statement polluted the namespace of the global frame and caused a name collision.

How to avoid name collisions?

%%mytutor -h 250
import math
print('{:.2f}, {:.2f}'.format(math.pow(-1, 2), pow(-1, 1 / 2)))

We can use the full name (fully-qualified name) math.pow prefixed with the module name (and possibly package names containing the module).

Can we shorten a name?

The name of a library can be very long and there can be a hierarchical structure as well.
E.g., to plot a sequence using pyplot module from matplotlib package:

%matplotlib inline
import matplotlib.pyplot
matplotlib.pyplot.stem([4, 3, 2, 1])
matplotlib.pyplot.ylabel(r'$x_n$')
matplotlib.pyplot.xlabel(r'$n$')
matplotlib.pyplot.title('A sequence of numbers')
matplotlib.pyplot.show()
../_images/Using Functions_43_0.png

It is common to rename matplotlib.pyplot as plt:

%matplotlib inline
import matplotlib.pyplot as plt
plt.stem([4, 3, 2, 1])
plt.ylabel(r'$x_n$')
plt.xlabel(r'$n$')
plt.title('A sequence of numbers')
plt.show()
../_images/Using Functions_45_0.png

We can also rename a function as we import it to avoid name collision:

from math import pow as fpow
fpow(2, 2), pow(2, 2)
(4.0, 4)

Exercise What is wrong with the following code?

import math as m
for m in range(5): m.pow(m, 2)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-14-53146a1609f3> in <module>
      1 import math as m
----> 2 for m in range(5): m.pow(m, 2)

AttributeError: 'int' object has no attribute 'pow'

There is a name collision: m is assigned to an integer in the for loop and so it is no longer the module math when calling m.pow.

Exercise Use the randint function from random to simulate the rolling of a die, by printing a random integer from 1 to 6.

import random
print(random.randint(1, 6))
3

6.4. Built-in Functions

How to learn more about a function such as randint?

There is a built-in function help for showing the docstring (documentation string).

import random
help(random.randint)  # random must be imported before
Help on method randint in module random:

randint(a, b) method of random.Random instance
    Return random integer in range [a, b], including both end points.
help(random)  # can also show the docstring of a module
help(help)

Does built-in functions belong to a module?

Indeed, every function must come from a module.

__builtin__.print('I am from the __builtin__ module.')
I am from the __builtin__ module.

__builtin__ module is automatically loaded because it provides functions that are commonly use for all programs.

How to list everything in a module?

We can use the built-in function dir (directory).

dir(__builtin__)

We can also call dir without arguments.
What does it print?

dir()