Using Functions

City University of Hong Kong

CS1302 Introduction to Computer Programming


%reload_ext divewidgets

Motivation

How to reuse code so we can write less?

Writing a loop is a simple way of code reuse because a piece of code is executed multiple times, once for each iteration.

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

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

The above computes the base-22 logarithm, log2(256)\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)

Unlike mathematical functions:

  • A python function may require no arguments, but we still need to call it with ().
input()
  • A python function may have side effects and return the value None.
x = print()
print(x, "of type", type(x))

An argument of a function call can be any expression.

print("1st input:", input(), "\n2nd input:", input())

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.)

Solution to Exercise 1
  • 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.

Import Functions from Modules

How to import functions?

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

%%optlite -h 300
from math import ceil, log10

x = 1234
print("Number of digits of x:", ceil(log10(x)))

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

How to import all functions from a library?

%%optlite -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?

%%optlite -h 500
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 or 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?

%%optlite -h 350
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 widget
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()

It is common to rename matplotlib.pyplot as plt:

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()

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)
%%optlite -h 500
import math as m

for m in range(5):
    m.pow(m, 2)
Solution to Exercise 2

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.

import random

print(random.randint(1, 6))

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(random)  # can also show the docstring of a module
help(help)

We can also print the source using the inspect module:

import inspect

print(inspect.getsource(random.randint))

Does built-in functions belong to a module?

Indeed, every function must come from a module.

__builtin__.print("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__)
dir()
dir?
Solution to Exercise 4

In the first line of the docstring:

If called without an argument, return the names in the current scope.