You're reading from Scientific Computing with Python 3
In mathematics, a function is written as a map that uniquely assigns an element y from the range R to every element x from the domain D.
This is expressed by f : D → R
Alternatively, when considering particular elements x and y, one writes f : x → y
Here, f is called the name of the function and f(x) is its value when applied to x. Here, x is sometimes called the argument of f. Let's first look at an example before considering functions in Python.
For example, D = ℝ x ℝ and y = f(x1, x2) = x1 - x2 . 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 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 f(x) for a given value of x
The first step is done once, while the second...
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.
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 objects. For instance, we may call the following:
z = 3 e = subtract(5,z)
Besides this standard way of calling a function, which is by passing 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 ...
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 in the function call is provided. 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. Refer to the following figure (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 definition, this is indicated by parameters prefixed...
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 z and returns its polar coordinate representation as magnitude r and angle according to Euler’s formula:
And the Python counterpart would be this:
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 formed
Here, we used the sqrt(x)
NumPy function for the square root of a number x
and arctan2(x,y)
for the expression tan-1(x/y).
Let us 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
; refer to Exercise 1.
If a function has no return
statement...
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 that you use 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, T0(x) =1, T1(x) = x.
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...
You should document your functions using a string at the beginning. This is called 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's newton.__doc__
. The minimal information you should provide in a docstring is the purpose of the function...
Functions are objects, like everything else in Python. One 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 functions 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 argument, make sure that f
has exactly the form described in the docstring of g
.
The docstring of fsolve
gives information about its func
parameter:
func -- A Python function or...
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.
Note
The name lambda originates from a special branch of calculus and mathematical logic, the -calculus.
For instance, to compute the following expression, we may use SciPy’s function quad
, which requires the function to be integrated as its first argument and the integration bounds as the next two arguments:
Here, the function to integrate is just a simple one-liner and we use the lambda
keyword 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 lambda
function can only consist of a single expression and in particular...
In the partial application section, 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 us start with the following situation:
Assume that we have a function that determines the 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 reshape
method. For instance, the how_sparse
function will not work with a list, because lists have no reshape
method. 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...
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 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.
Ex 1 → Write a function polar_to_comp
, which takes two arguments r 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
, (refer to [8] for more detail on functools) 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 example in section Function as decorators).
Ex 4 → A continuous function f with f(a)f(b) < 0 changes its sign in the interval [a, b] and has at least one root...