2. Functions#

2.1. Function calls#

In the context of programming, a function is a named sequence of statements that performs a computation. When you define a function, you specify the name and the sequence of statements. Later, you can “call” the function by name.
We have already seen one example of a function call :

type(32)
int

The name of the function is type. The expression in parentheses is called the argument of the function. The result, for this function, is the type of the argument.

It is common to say that a function “takes” an argument and “returns” a result. The result is called the return value.

2.2. Conversion functions#

Python has functions which help you convert between the different python types.

2.2.1. Example 1#

The following code will not work.

# this will not work ...
x = 3.14
y = "5.6"
print(x*y)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [2], line 4
      2 x = 3.14
      3 y = "5.6"
----> 4 print(x*y)

TypeError: can't multiply sequence by non-int of type 'float'
# ... but it can be fixed
y = float(y)
print(x*y)
17.584

2.2.2. Example 2#

z = "pi = " + str(3.14159)
print(z)
pi = 3.14159

2.2.3. Example 3#

z1 = complex(1,2)
print(z1)
print(z1*z1)
(1+2j)
(-3+4j)

As example 3 demonstrates, the conversion functions also provide a way of “initialising” some of the more complex python type.

2.3. Defining new functions#

A function definition specifies the name of a new function and the sequence of statements that execute when the function is called.

def f(x) :
    y = 1/(1+x)
    return y

def is a keyword that indicates that this is a function definition. The name of the function is f. The rules for function names are the same as for variable names: letters, numbers and some punctuation marks are legal, but the first character can’t be a number. You can’t use a keyword as the name of a function, and you should avoid having a variable and a function with the same name.

The x in the parenthesis is an argument that is passed to the function. A function can have a comma separated list of arguments.

def g(x,n) :
    y = 1/(1+x)**n
    return y

The body of the function is indented with a tab, (or 4 spaces), to show it is part of the function. The return statement contains an expression which is what the function evaluates to when it is used.

g(2.1,5)
0.003492943259127733

2.3.1. Exercise 1#

What is the type of g ?

2.3.2. Solution 1#

type(g)
function

The arguments of a function are variables that name each argument. These can be used explicity when using a function.

def f(a,b,c) :
    return(a+2*b+3*c)

2.3.3. Exercise 2#

Predict the output of the follwing code.

f(1,2,3)
14
f(a=1,b=2,c=3)
14
f(a=1,c=3,b=2)
14
f(1,2,c=3)
14
f(1,c=3,2)
  File "<ipython-input-48-6cd2a3b71e21>", line 1
    f(1,c=3,2)
             ^
SyntaxError: positional argument follows keyword argument

2.4. Functions and scope#

Consider the following python code

def f(x,y,z) :
    a = 1
    b = 2
    c = 3
    return(a*x+b*y+c*z)
f(1,2,3)
print(a)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-50-aeb7dbfeb3ff> in <module>
      1 f(1,2,3)
----> 2 print(a)

NameError: name 'a' is not defined

The function appears to work ok, but the print statement does not. This is because variables declared in a function are local to that function. This is typically stated as the variable a is contained in the scope of the function f. However, the following will work :

a = 5
def f(x,y,z) :
    a = 1
    b = 2
    c = 3
    return(a*x+b*y+c*z)
f(1,2,3)
print(a)
5

2.4.1. Exercise 3#

What would you expect the output of the following code to be ?

val = False

def exercise3():
    val = True
    
print(val)
exercise3()
print(val)
False
False

The exercise3 function creates a new local variable named val and assigns it the value TRUE. This local variable goes away when the function ends, and has no effect on the global variable.

To reassign a global variable inside a function you have to “declare” the global variable before you use it:

def exercise3():
    global val
    val = True

val = False
print(val)
exercise3()
print(val)
False
True

2.4.2. Exercise 4#

The following code shows one way of obtaining the real and imaginary parts of a complex number.

z = 2 + 3j
print(z.real)
print(z.imag)
2.0
3.0

Write a function to determine the conjugate of a complex number.

def conjugate(z) :
    return(z.real - z.imag*(0+1j))
print(conjugate(z))
(2-3j)

2.5. Higher order programming#

In Python, as in some other programming languages, functions have a specific type and can be used like ordinary variables. For example, the following is valid, (and sensible), python (try it).

def f(x,y,z) :
    return(x+2*y+3*z)

g = f
print(g(1,2,3))
print(type(f))
print(type(g))
print(g==f)
14
<class 'function'>
<class 'function'>
True

Given the above definition of f, the following is also valid.

def h(x,func) :
    return(3*func(2*x,2,3))

2.5.1. Exercise 5#

What is the output of the following code ?

print(h(3,f))
57

So, functions have type, can be assigned to variables, and passed to functions.

2.6. Accessing functions from python libraries using import#

A library in python is a collection of functions, classes, and data that are organised in a group of files specific way. Using this specific way to organise the files allows them to be loaded into another python program using the import directive. There are several ways that this can be done, each with its own advantages. The following example is perhaps the simplest (but not particularly efficient) way of accessing the contents of a python library.

import math

This imports the math package and all of its functions, classes, data and global variables. For example, the math package has the function sin. Try running the following python code to see what it does.

x = 1.21
y = math.sin(x)
print(y)
0.9356160015533859

Notice how the function sin is used. The “.”” between math and sin indicates that the sin function is in the math library. You can get information on a library by using the python help function.

help("math")
Help on module math:

NAME
    math

MODULE REFERENCE
    https://docs.python.org/3.9/library/math
    
    The following documentation is automatically generated from the Python
    source files.  It may be incomplete, incorrect or include features that
    are considered implementation detail and may vary between Python
    implementations.  When in doubt, consult the module reference at the
    location listed above.

DESCRIPTION
    This module provides access to the mathematical functions
    defined by the C standard.

FUNCTIONS
    acos(x, /)
        Return the arc cosine (measured in radians) of x.
        
        The result is between 0 and pi.
    
    acosh(x, /)
        Return the inverse hyperbolic cosine of x.
    
    asin(x, /)
        Return the arc sine (measured in radians) of x.
        
        The result is between -pi/2 and pi/2.
    
    asinh(x, /)
        Return the inverse hyperbolic sine of x.
    
    atan(x, /)
        Return the arc tangent (measured in radians) of x.
        
        The result is between -pi/2 and pi/2.
    
    atan2(y, x, /)
        Return the arc tangent (measured in radians) of y/x.
        
        Unlike atan(y/x), the signs of both x and y are considered.
    
    atanh(x, /)
        Return the inverse hyperbolic tangent of x.
    
    ceil(x, /)
        Return the ceiling of x as an Integral.
        
        This is the smallest integer >= x.
    
    comb(n, k, /)
        Number of ways to choose k items from n items without repetition and without order.
        
        Evaluates to n! / (k! * (n - k)!) when k <= n and evaluates
        to zero when k > n.
        
        Also called the binomial coefficient because it is equivalent
        to the coefficient of k-th term in polynomial expansion of the
        expression (1 + x)**n.
        
        Raises TypeError if either of the arguments are not integers.
        Raises ValueError if either of the arguments are negative.
    
    copysign(x, y, /)
        Return a float with the magnitude (absolute value) of x but the sign of y.
        
        On platforms that support signed zeros, copysign(1.0, -0.0)
        returns -1.0.
    
    cos(x, /)
        Return the cosine of x (measured in radians).
    
    cosh(x, /)
        Return the hyperbolic cosine of x.
    
    degrees(x, /)
        Convert angle x from radians to degrees.
    
    dist(p, q, /)
        Return the Euclidean distance between two points p and q.
        
        The points should be specified as sequences (or iterables) of
        coordinates.  Both inputs must have the same dimension.
        
        Roughly equivalent to:
            sqrt(sum((px - qx) ** 2.0 for px, qx in zip(p, q)))
    
    erf(x, /)
        Error function at x.
    
    erfc(x, /)
        Complementary error function at x.
    
    exp(x, /)
        Return e raised to the power of x.
    
    expm1(x, /)
        Return exp(x)-1.
        
        This function avoids the loss of precision involved in the direct evaluation of exp(x)-1 for small x.
    
    fabs(x, /)
        Return the absolute value of the float x.
    
    factorial(x, /)
        Find x!.
        
        Raise a ValueError if x is negative or non-integral.
    
    floor(x, /)
        Return the floor of x as an Integral.
        
        This is the largest integer <= x.
    
    fmod(x, y, /)
        Return fmod(x, y), according to platform C.
        
        x % y may differ.
    
    frexp(x, /)
        Return the mantissa and exponent of x, as pair (m, e).
        
        m is a float and e is an int, such that x = m * 2.**e.
        If x is 0, m and e are both 0.  Else 0.5 <= abs(m) < 1.0.
    
    fsum(seq, /)
        Return an accurate floating point sum of values in the iterable seq.
        
        Assumes IEEE-754 floating point arithmetic.
    
    gamma(x, /)
        Gamma function at x.
    
    gcd(*integers)
        Greatest Common Divisor.
    
    hypot(...)
        hypot(*coordinates) -> value
        
        Multidimensional Euclidean distance from the origin to a point.
        
        Roughly equivalent to:
            sqrt(sum(x**2 for x in coordinates))
        
        For a two dimensional point (x, y), gives the hypotenuse
        using the Pythagorean theorem:  sqrt(x*x + y*y).
        
        For example, the hypotenuse of a 3/4/5 right triangle is:
        
            >>> hypot(3.0, 4.0)
            5.0
    
    isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)
        Determine whether two floating point numbers are close in value.
        
          rel_tol
            maximum difference for being considered "close", relative to the
            magnitude of the input values
          abs_tol
            maximum difference for being considered "close", regardless of the
            magnitude of the input values
        
        Return True if a is close in value to b, and False otherwise.
        
        For the values to be considered close, the difference between them
        must be smaller than at least one of the tolerances.
        
        -inf, inf and NaN behave similarly to the IEEE 754 Standard.  That
        is, NaN is not close to anything, even itself.  inf and -inf are
        only close to themselves.
    
    isfinite(x, /)
        Return True if x is neither an infinity nor a NaN, and False otherwise.
    
    isinf(x, /)
        Return True if x is a positive or negative infinity, and False otherwise.
    
    isnan(x, /)
        Return True if x is a NaN (not a number), and False otherwise.
    
    isqrt(n, /)
        Return the integer part of the square root of the input.
    
    lcm(*integers)
        Least Common Multiple.
    
    ldexp(x, i, /)
        Return x * (2**i).
        
        This is essentially the inverse of frexp().
    
    lgamma(x, /)
        Natural logarithm of absolute value of Gamma function at x.
    
    log(...)
        log(x, [base=math.e])
        Return the logarithm of x to the given base.
        
        If the base not specified, returns the natural logarithm (base e) of x.
    
    log10(x, /)
        Return the base 10 logarithm of x.
    
    log1p(x, /)
        Return the natural logarithm of 1+x (base e).
        
        The result is computed in a way which is accurate for x near zero.
    
    log2(x, /)
        Return the base 2 logarithm of x.
    
    modf(x, /)
        Return the fractional and integer parts of x.
        
        Both results carry the sign of x and are floats.
    
    nextafter(x, y, /)
        Return the next floating-point value after x towards y.
    
    perm(n, k=None, /)
        Number of ways to choose k items from n items without repetition and with order.
        
        Evaluates to n! / (n - k)! when k <= n and evaluates
        to zero when k > n.
        
        If k is not specified or is None, then k defaults to n
        and the function returns n!.
        
        Raises TypeError if either of the arguments are not integers.
        Raises ValueError if either of the arguments are negative.
    
    pow(x, y, /)
        Return x**y (x to the power of y).
    
    prod(iterable, /, *, start=1)
        Calculate the product of all the elements in the input iterable.
        
        The default start value for the product is 1.
        
        When the iterable is empty, return the start value.  This function is
        intended specifically for use with numeric values and may reject
        non-numeric types.
    
    radians(x, /)
        Convert angle x from degrees to radians.
    
    remainder(x, y, /)
        Difference between x and the closest integer multiple of y.
        
        Return x - n*y where n*y is the closest integer multiple of y.
        In the case where x is exactly halfway between two multiples of
        y, the nearest even value of n is used. The result is always exact.
    
    sin(x, /)
        Return the sine of x (measured in radians).
    
    sinh(x, /)
        Return the hyperbolic sine of x.
    
    sqrt(x, /)
        Return the square root of x.
    
    tan(x, /)
        Return the tangent of x (measured in radians).
    
    tanh(x, /)
        Return the hyperbolic tangent of x.
    
    trunc(x, /)
        Truncates the Real x to the nearest Integral toward 0.
        
        Uses the __trunc__ magic method.
    
    ulp(x, /)
        Return the value of the least significant bit of the float x.

DATA
    e = 2.718281828459045
    inf = inf
    nan = nan
    pi = 3.141592653589793
    tau = 6.283185307179586

FILE
    /home/grosedj/.pyenv/versions/3.9.0/lib/python3.9/lib-dynload/math.cpython-39-x86_64-linux-gnu.so

Notice that the name of the library is in quotation marks. You can also use help to get help on the functions in the library.

help("math.tan")
Help on built-in function tan in math:

math.tan = tan(x, /)
    Return the tangent of x (measured in radians).

You can also import just specific parts of a library and use them without the “.”.

from math import tan
y = tan(x)
print(y)
2.650324594970601

When importing a function you can also rename it.

from math import atan as arctan
z = arctan(y)
print(z)
1.21

2.6.1. Exercise 7#

Import the random library and use help to discover some of its functions. Pick some of the functions and experiment with them.

2.7. Installing python packages with pip#

Not all libraries come with python by default. For example, matplotlib is a popular python plotting library. This library is covered in more detail in a later chapter, but for now, this is how it can be installed

!python3 -m pip install matplotlib
Requirement already satisfied: matplotlib in /home/grosedj/python-envs/further-python-env/env/lib/python3.9/site-packages (3.4.1)
Requirement already satisfied: pyparsing>=2.2.1 in /home/grosedj/python-envs/further-python-env/env/lib/python3.9/site-packages (from matplotlib) (2.4.7)
Requirement already satisfied: numpy>=1.16 in /home/grosedj/python-envs/further-python-env/env/lib/python3.9/site-packages (from matplotlib) (1.20.2)
Requirement already satisfied: pillow>=6.2.0 in /home/grosedj/python-envs/further-python-env/env/lib/python3.9/site-packages (from matplotlib) (8.2.0)
Requirement already satisfied: python-dateutil>=2.7 in /home/grosedj/python-envs/further-python-env/env/lib/python3.9/site-packages (from matplotlib) (2.8.1)
Requirement already satisfied: cycler>=0.10 in /home/grosedj/python-envs/further-python-env/env/lib/python3.9/site-packages (from matplotlib) (0.10.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /home/grosedj/python-envs/further-python-env/env/lib/python3.9/site-packages (from matplotlib) (1.3.1)
Requirement already satisfied: six>=1.5 in /home/grosedj/python-envs/further-python-env/env/lib/python3.9/site-packages (from python-dateutil>=2.7->matplotlib) (1.15.0)
WARNING: You are using pip version 20.2.3; however, version 21.0.1 is available.
You should consider upgrading via the '/home/grosedj/python-envs/further-python-env/env/bin/python3 -m pip install --upgrade pip' command.

In the above example, the ! has been used before the python3 command. The ! instructs Jupyter to run the program with the name after the ! i.e. python3 in this case. The -m option instructs python to run another program, in this case, the python utility pip. Getting python to run another python program such as pip ensures that the other program uses the same python environment on you system.

The next option, install, instructs pip to install the library from the “python package index”, which is often referred to as PyPI. Finally, the name of the package to be installed is provided. In this case, matplotlib.

Note, if you already have matplotlib installed, running this command will not have any negative effects.

Functions from the matplotlib library can now be used.

from matplotlib import pyplot as plt
plt.plot([0,1,4,9,16])
plt.show()
_images/functions_77_0.png

2.8. Closures#

In Python, (and most other languages that support higher order programming), functions can be defined inside other functions. Such a function is called a closure.

def f() :
    def g(x) :
        return(2*x)
    return(g)

h = f()
h(3)
6

The following example makes clearer what the value of closures might be. Try it out and check it works.

import math

def logistic_function(k,x0,L) :
    def logistic(x) :
        res = math.exp(-k*(x-x0))
        res += 1
        res = L/res
        return(res)
    return(logistic)

f = logistic_function(1,0,1)
print(f(3))
0.9525741268224334

2.8.1. Exercise 8#

What do you think the utility of the code in the previous exercise might be ?