Reader small image

You're reading from  Scientific Computing with Python - Second Edition

Product typeBook
Published inJul 2021
Reading LevelIntermediate
PublisherPackt
ISBN-139781838822323
Edition2nd Edition
Languages
Right arrow
Authors (3):
Claus Führer
Claus Führer
author image
Claus Führer

Claus Führer is a professor of scientific computations at Lund University, Sweden. He has an extensive teaching record that includes intensive programming courses in numerical analysis and engineering mathematics across various levels in many different countries and teaching environments. Claus also develops numerical software in research collaboration with industry and received Lund University's Faculty of Engineering Best Teacher Award in 2016.
Read more about Claus Führer

View More author details
Right arrow
Functions

This chapter introduces functions, a fundamental building block in programming. We show how to define them, how to handle input and output, how to properly use them, and how to treat them as objects.

The following topics are covered in this chapter:

  • Functions in mathematics and functions in Python
  • Parameters and arguments
  • Return values
  • Recursive functions
  • Function documentation
  • Functions are objects
  • Anonymous functions the keyword lambda
  • Functions as decorators

7.1 Functions in mathematics and functions in Python

In mathematics, a function is written as a map that uniquely assigns to every element from the domain a corresponding element from the range .

This is expressed by .

Alternatively, when considering particular elements and , you write .

Here, is called the name of the function and is its value when applied to . Here, is sometimes called the argument of . Let's first look at an example before considering functions in Python.

For example, and . This function maps two real numbers to their difference.

In mathematics, functions can have numbers, vectors, matrices, and even other functions as arguments. Here is an example of a function with mixed arguments:

In this case, a real number is returned. When working with functions, we have to distinguish between two different steps:

  • The definition of the function
  • The evaluation of the function, that is, the computation of for a given value of

The first step is done once, while...

7.2 Parameters and arguments

When defining a function, its input variables are called the parameters of the function. The input used when executing the function is called its argument.

7.2.1 Passing arguments by position and by keyword

We will consider the previous example again, where the function takes two parameters, namely x1 and x2.

Their names serve to distinguish the two numbers, which in this case cannot be interchanged without altering the result. The first parameter defines the number from which the second parameter is subtracted. When subtract is called, every parameter is replaced by an argument. Only the order of the arguments matters; the arguments can be any object. For instance, we may call the following:

z = 3 
e = subtract(5,z)

Besides this standard way of calling a function, which is bypassing the arguments by position, it might sometimes be convenient to pass arguments using keywords. The names of the parameters are the keywords; consider the following instance:

z = 3 
e = subtract(x2 = z, x1 = 5)

Here, the arguments are assigned to the parameters by name and not by position in the call. Both ways of calling a function can be combined so...

7.2.2 Changing arguments

The purpose of parameters is to provide the function with the necessary input data. Changing the value of the parameter inside the function normally has no effect on its value outside the function:

def subtract(x1, x2):
    z = x1 - x2
    x2 = 50.
    return z
a = 20.
b = subtract(10, a)    # returns -10
a    # still has the value 20

This applies to all immutable arguments, such as strings, numbers, and tuples. The situation is different if mutable arguments, such as lists or dictionaries, are changed.

For example, passing mutable input arguments to a function and changing them inside the function can change them outside the function too:

def subtract(x):
    z = x[0] - x[1]
    x[1] = 50.
    return z
a = [10,20]
b = subtract(a)    # returns -10
a    # is now [10, 50.0]

Such a function misuses its arguments to return results. We strongly dissuade you from such constructions and recommend that you do not change input arguments inside the function (for more information...

7.2.3 Access to variables defined outside the local namespace

Python allows functions to access variables defined in any of its enclosing program units. These are called global variables, in contrast to local variables. The latter is only accessible within the function. For example, consider the following code:

import numpy as np # here the variable np is defined
def sqrt(x):
    return np.sqrt(x) # we use np inside the function

This feature should not be abused. The following code is an example of what not to do:

a = 3
def multiply(x):
    return a * x # bad style: access to the variable a defined outside

When changing the variable a, the function multiply tacitly changes its behavior:

a=3
multiply(4)  # returns 12
a=4  
multiply(4)  # returns 16

It is much better, in that case, to provide the variable as a parameter through the argument list:

def multiply(x, a):
    return a * x

Global variables can be useful when working with closures; see also the related example in Section 7.7: Anonymous...

7.2.4 Default arguments

Some functions can have many parameters, and among them, some might only be of interest in nonstandard situations. It would be practical if arguments could automatically be set to standard (default) values.

We demonstrate the use of default arguments by looking at the command norm in the module scipy.linalg. It computes various norms of matrices and vectors. More on matrix norms can be found in [10 ,§2.3].

The following calls for computing the Frobenius norm of the identity matrix are equivalent:

import scipy.linalg as sl
sl.norm(identity(3))
sl.norm(identity(3), ord = 'fro')
sl.norm(identity(3), 'fro')

Note that in the first call, no information about the keyword ord is given. How does Python know that it should compute the Frobenius norm and not another norm, for example, the Euclidean 2-norm?

The answer to the previous question is the use of default values. A default value is a value already given by the function definition. If...

Beware of mutable default arguments

The default arguments are set upon function definition. Changing mutable arguments inside a function has a side effect when working with default values, for example:

def my_list(x1, x2 = []):
    x2.append(x1)
    return x2
my_list(1)  # returns [1]
my_list(2)  # returns [1,2]

Recall, lists are mutable objects.

7.2.5 Variable number of arguments

Lists and dictionaries may be used to define or call functions with a variable number of arguments. Let's define a list and a dictionary as follows:

data = [[1,2],[3,4]]    
style = dict({'linewidth':3,'marker':'o','color':'green'})

Then we can call the plot function using starred (*) arguments:

plot(*data,**style)

A variable name prefixed by *, such as *data in the preceding example, means that a list that gets unpacked to provide the function with its arguments. In this way, a list generates positional arguments. Similarly, a variable name prefixed by **, such as **style in the example, unpacks a dictionary to keyword arguments; see Figure 7.1:

Figure 7.1: Starred arguments in function calls

You might also want to use the reverse process, where all given positional arguments are packed into a list and all keyword arguments are packed into a dictionary when passed to a function. In the function...

7.3 Return values

A function in Python always returns a single object. If a function has to return more than one object, these are packed and returned as a single tuple object.

For instance, the following function takes a complex number and returns its polar coordinate representation as magnitude and angle :

def complex_to_polar(z):
    r = sqrt(z.real ** 2 + z.imag ** 2)
    phi = arctan2(z.imag, z.real)
    return (r,phi)  # here the return object is formedcite

(See also Euler’s formula, .)

Here, we used the NumPy function sqrt(x) for the square root of a number x and arctan2(x,y) for the expression .

Let's try our function:

z = 3 + 5j  # here we define a complex number
a = complex_to_polar(z)
r = a[0]
phi = a[1]

The last three statements can be written more elegantly in a single line:

r,phi = complex_to_polar(z)

We can test our function by calling polar_to_comp defined in Exercise 1 in the Exercises section.

If a function has no return statement, it returns the value...

7.4 Recursive functions

In mathematics, many functions are defined recursively. In this section, we will show how this concept can be used even when programming a function. This makes the relation of the program to its mathematical counterpart very clear, which may ease the readability of the program.

Nevertheless, we recommend using this programming technique with care, especially within scientific computing. In most applications, the more straightforward iterative approach is more efficient. This will become immediately clear from the following example.

Chebyshev polynomials are defined by a three-term recursion:

Such a recursion needs to be initialized, that is, .

In Python, this three-term recursion can be realized by the following function definition:

def chebyshev(n, x):
    if n == 0:
        return 1.
    elif n == 1:
        return x
    else:
        return 2. * x * chebyshev(n - 1, x) \
                      - chebyshev(n - 2 ,x)

To compute , the function is then called like...

7.5 Function documentation

You should document your functions using a string at the beginning. This string is called a docstring:

def newton(f, x0):
    """
    Newton's method for computing a zero of a function
    on input:
    f  (function) given function f(x)
    x0 (float) initial guess 
    on return:
    y  (float) the approximated zero of f
    """
    ...

When calling help(newton), you get this docstring displayed together with the call of this function:

Help on function newton in module __main__:

newton(f, x0)
     Newton's method for computing a zero of a function
     on input:
     f  (function) given function f(x)
     x0 (float) initial guess
     on return:
     y  (float) the approximated zero of f

The docstring is internally saved as an attribute, __doc__, of the given function. In the example, it is newton.__doc__. The minimal information you should provide in a docstring is the purpose of the function and the description of...

7.6 Functions are objects

Functions are objects, like everything else in Python. You may pass functions as arguments, change their names, or delete them. For example:

def square(x):
    """
    Return the square of x
    """
    return x ** 2
square(4) # 16
sq = square # now sq is the same as square
sq(4) # 16
del square # square doesn't exist anymore
print(newton(sq, .2)) # passing as argument

Passing functions as arguments is very common when applying algorithms in scientific computing. The function fsolve in scipy.optimize for computing a zero of a given function or quad in scipy.integrate for computing integrals are typical examples.

A function itself can have a different number of arguments with differing types. So, when passing your function f to another function g as an argument, make sure that f has exactly the form described in the docstring of g.

The docstring of fsolve gives information about its parameter func:

 fun c -- A Python function...

7.6.1 Partial application

Let's start with an example of a function with two variables.

The function can be viewed as a function in two variables. Often you consider not as a free variable but as a fixed parameter of a family of functions :

This interpretation reduces a function in two variables to a function in one variable given a fixed parameter value . The process of defining a new function by fixing (freezing) one or several parameters of a function is called partial application.

Partial applications are easily created using the Python module functools, which provides a function called partial for precisely this purpose. We illustrate this by constructing a function that returns a sine for a given frequency:

import functools 
def sin_omega(t, freq):
return sin(2 * pi * freq * t)

def make_sine(frequency):
return functools.partial(sin_omega, freq = frequency)

fomega=make_sine(0.25)
fomega(3) # returns -1.0

In the last line, the newly created function is evaluated at .

7.6.2 Using closures

Using the view that functions are objects, partial applications can be realized by writing a function, which itself returns a new function, with a reduced number of input arguments. For instance, the function make_sine could be defined as follows:

def make_sine(freq):
    "Make a sine function with frequency freq"
    def mysine(t):
        return sin_omega(t, freq)
    return mysine

In this example, the inner function mysine has access to the variable freq; it is neither a local variable of this function nor is it passed to it via the argument list. Python allows such a construction, see Section 13.1, Namespaces.

7.7 Anonymous functions the keyword lambda

The keyword lambda is used in Python to define anonymous functions, that is, functions without a name and described by a single expression. You might just want to perform an operation on a function that can be expressed by a simple expression without naming this function and without defining this function by a lengthy def block.

The name lambda originates from a special branch of calculus and mathematical logic, the -calculus.

We demonstrate the use of lambda-functions by numerically evaluating the following integral:

We use SciPy’s function quad, which requires as its first argument the function to be integrated and the integration bounds as the next two arguments. Here, the function to be integrated is just a simple one-liner and we use the keyword lambda to define it:

import scipy.integrate as si
si.quad(lambda x: x ** 2 + 5, 0, 1)

The syntax is as follows:

lambda parameter_list: expression

The definition of the function lambda...

7.7.1 The lambda construction is always replaceable

It is important to note that the lambda construction is only syntactic sugar in Python. Any lambda construction may be replaced by an explicit function definition:

parabola = lambda x: x**2+5 
# the following code is equivalent
def parabola(x):
    return x ** 2 + 5

The main reason to use this construction is for very simple functions when a full function definition would be too cumbersome.

lambda functions provide a third way to make closures as we demonstrate by continuing with the previous example, .

We use the function sin_omega from Section 7.6.1, Partial Applicationto compute the integral of the sine function for various frequencies:

import scipy.integrate as si
for iteration in range(3):
    print(si.quad(lambda x: sin_omega(x, iteration*pi), 0, pi/2.) )

7.8 Functions as decorators

In Section 7.6.1: Partial application, we saw how a function can be used to modify another function. A decorator is a syntax element in Python that conveniently allows us to alter the behavior of a function without changing the definition of the function itself. Let's start with the following situation.

Assume that we have a function that determines tcitehe degree of sparsity of a matrix:

def how_sparse(A):
    return len(A.reshape(-1).nonzero()[0])

This function returns an error if it is not called with an array object as input. More precisely, it will not work with an object that does not implement the method reshape. For instance, the function how_sparse will not work with a list, because lists have no method reshape. The following helper function modifies any function with one input parameter so that it tries to make a type conversion to an array:

def cast2array(f):
    def new_function(obj):
        fA = f(array(obj))
        return fA
    return...

7.9 Summary

Functions are not only the ideal tools for making your program modular, but they also reflect mathematical thinking. You learned the syntax of function definitions and how to distinguish between defining and calling a function.

We considered functions as objects that can be modified by other functions. When working with functions, it is important to be familiar with the notion of the scope of a variable and how information is passed into a function by parameters.

Sometimes, it is convenient to define functions on the fly with so-called anonymous functions. For this, we introduced the keyword lambda.

7.10 Exercises

Ex 1: Write a function polar_to_comp, which takes two arguments and and returns the complex number . Use the NumPy function exp for the exponential function.

Ex 2: In the description of the Python module functools, [8], you find the following Python function:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

Explain and test this function.

Ex 3: Write a decorator for the function how_sparse, which cleans the input matrix A by setting the elements that are less than 1.e-16 to zero (consider the example in Section 7.8: Functions as decorators).

Ex 4: A continuous function with changes its sign in the interval and has at least one root (zero) in this interval. Such a root can be found with the bisection method...

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Scientific Computing with Python - Second Edition
Published in: Jul 2021Publisher: PacktISBN-13: 9781838822323
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Authors (3)

author image
Claus Führer

Claus Führer is a professor of scientific computations at Lund University, Sweden. He has an extensive teaching record that includes intensive programming courses in numerical analysis and engineering mathematics across various levels in many different countries and teaching environments. Claus also develops numerical software in research collaboration with industry and received Lund University's Faculty of Engineering Best Teacher Award in 2016.
Read more about Claus Führer