This lecture explains branching statements, how to define functions in Python, and all other important details of functions in Python.


Branching in Python

Consider the last problem in homework 4, where we defined a dictionary of people names and their roles in our ECL class.

ecl_dict = { 'nicholas boeker'  : 'student'
           , 'bradley bridges'  : 'student'
           , 'sagar dhana'      : 'student' 
           , 'travis driver'    : 'student'
           , 'eric gagliano'    : 'student'
           , 'christian garcia' : 'student'
           , 'matthew goree'    : 'student'
           , 'lucero herrera'   : 'student'
           , 'jake janssen'     : 'student'
           , 'michael langford' : 'student'
           , 'colin lewis'      : 'student'
           , 'mark loveland'    : 'student'
           , 'emilio mendiola'  : 'student'
           , 'kreshel nguyen'   : 'student'
           , 'russell philley'  : 'student'
           , 'caleb phillips'   : 'student'
           , 'joseph robbins'   : 'student'
           , 'bradley smith'    : 'student'
           , 'vivek varier'     : 'assistant'
           , 'amir shahmoradi'  : 'instructor'
           }


Now suppose we would like to write a Python script that, when executed, asks the user to input the full name of a person in our class and then outputs on screen, the role of the person in class. To do this, first we should get familiar with Python’s built-in function input().

ATTENTION: Python 2 Alert!

In Python 2, the corresponding function is raw_input(). In Python 3, it is now renamed to input().
In [11]: input("\n Please enter the full name of the person: ")

 Please enter the full name of the person: Amir Shahmoradi
Out[11]: 'Amir Shahmoradi'


The function input() is a Python built-in function that outputs the input string inside parantheses to the output screen, and then waits for the user to enter an input. This function reads a line from input, and converts it to a string (stripping a trailing newline), and returns that. One can also put the user’s input directly into a variable, which is the normal way of using this function.

In [13]: input_variable = input("\n Please enter the full name of the person: ")

 Please enter the full name of the person: Amir Shahmoradi

In [14]: print(input
input          input_key      input_variable

In [14]: print(input_variable)
Amir Shahmoradi

In [15]: type(input_variable)
Out[15]: str

In [16]: input_variable = input("\n Please enter the full name of the person: ")

 Please enter the full name of the person: 1234

In [17]: type(input_variable)   # whatever type the input is, it will be converted to string by Python interpreter
Out[17]: str


Now, back to our originally stated problem in the above, we want to write a program that takes in the name of a person from the command line and then tells the user some information about them, depending on their name. To achive this, we need to become familiar with the concept of branching in Python. Specifically, we can achieve our goal by writing a if-elif statement in Python as in the following python script.

The general syntax for if blocks in python is the following.

if expression1:     # if expression1 is True
   statement(s)         # execute the required Python statements
elif expression2:   # else if expression2 is True
   statement(s)         # execute the required Python statements
elif expression3:   # else if expression3 is True
   statement(s)         # execute the required Python statements
else:               # else if neither of the above were True
   statement(s)


Note that the indentations at beginning of each of the statements is necessary, otherwise the Python interpreter will give you a syntax error. However, if it important to note also that the number of indentations is arbitrary.

ATTENTION!

I highly recommend you to be consistent in indentations of your Python code. The whole point of Python is to write a highly-human-readable code. and this requires you to write your code in the most consistent way possible. For example, I recommend you to always use either 2,3, or 4 white-space characters for indentations in your code, and

AVOID USING tab CHARACTER AS INDENTATION IN YOUR CODES

unless your editor automatically converts it to a pre-specified number of white-space characters. The tab character can create a huge mess in your Python codes, and some extra work to clean them up. Here is a good resource to learn more about indentations in Python
#!/usr/bin/env python
ecl_dict = { 'nicholas boeker'  : 'student'
           , 'bradley bridges'  : 'student'
           , 'sagar dhana'      : 'student' 
           , 'travis driver'    : 'student'
           , 'eric gagliano'    : 'student'
           , 'christian garcia' : 'student'
           , 'matthew goree'    : 'student'
           , 'lucero herrera'   : 'student'
           , 'jake janssen'     : 'student'
           , 'michael langford' : 'student'
           , 'colin lewis'      : 'student'
           , 'mark loveland'    : 'student'
           , 'emilio mendiola'  : 'student'
           , 'kreshel nguyen'   : 'student'
           , 'russell philley'  : 'student'
           , 'caleb phillips'   : 'student'
           , 'joseph robbins'   : 'student'
           , 'bradley smith'    : 'student'
           , 'vivek varier'     : 'assistant'
           , 'amir shahmoradi'  : 'instructor'
           }
name = input("\n Please enter the full name of the person: ")
if name in ecl_dict:    # First make sure the name is in our dictionary
    if ecl_dict[name] == 'instructor':
        print( '\nThe name you enetered: {} belongs to the instructor of the ECL course. His office hours are Mondays 5-6 p.m.\n'.format(name) )
    elif ecl_dict[name] == 'assistant':
        print( '\nThe name you enetered: {} belongs to the Teaching Assistant of the ECL course. His office hours are Tuesdays 9-11 a.m.\n'.format(name) )
    elif ecl_dict[name] == 'student':
        print( '\nThe name you enetered: {} belongs to one of the amazing students in our class. You can certainly reach him during the weekly ECL classes on Wednesdays 9-10 a.m.\n'.format(name) )
else:
    print('\nThe name you entered: {} does not correspond to any real person in ECL class. Make sure you are not looking for a ghost, as our class is ghost-free.\n'.format(name) )


Now, if we run the file containing this script, we will get something like the following,

$ ./ecl_names.py

 Please enter the full name of the person: amir shahmoradi

The name you enetered: amir shahmoradi belongs to the instructor of the ECL course. His office hours are Mondays 5-6 p.m.

$ ./ecl_names.py

 Please enter the full name of the person: Harry Potter

The name you entered: Harry Potter does not correspond to any real person in ECL class. Make sure you are not looking for a ghost, as our class is ghost-free.


Conditional if statement is the only built-in branching method in Python. However, it can be written in several different sytaxes, each of which can be useful in some circumstances:

1. One-line conditional statement:

if condition: statement


For example:

if sqrt(2) < 2: print('sqrt(2) < 2\nOf course that was obvious!')
sqrt(2) < 2
Of course that was obvious!

2. multiple line (as stated above) conditional statement:

if condition:
    block statements
elif:
    block statements
else:
    block statements


3. Inline conditional expression: This is a particularly useful syntax for conditional value assignments in Python.

expression1 if condition_is_met else expression2


For example, instead of writing,

if condition:
    a = value1
else
    a = value2


one can summarize it all in one line,

a = (value1 if condition else value2)


Note that the paratheses are not necessary, however, they are recommended for clarity. Here is an example code,

name = input('Input the name: ')
print( 'This person is the ECL instructor' if name == 'amir' else 'This person is not the ECL instructor')
Input the name: amir
This person is the ECL instructor


Non-boolean conditions in if-statements

There is a rather interesting feature of conditions in Python if-statements, that allows the programmer to use a non-boolean variable or value type directly in place of the condition in if-statement. What really happens here is that, Python interpreter converts the non-boolean type to a boolean value, when it occurs in place of an if-statement condition.

if 5.6:
    print('The condition in this if statement is automatically converted from float to boolean')
The condition in this if statement is automatically converted from float to boolean
if 0.0:
    print('A float value of zero is converted to False')
if not 0.0:
    print('A float value of zero is converted to False')
A float value of zero is converted to False
if 0.000000000000000000000000000000000000000000000000000000000000000000001:
    print('Any non-zero float value of any precision is converted to True')
A float value of zero is converted to False
if 1.e-323:
    print('Any non-zero float value of any precision is converted to True')
Any non-zero float value of any precision is converted to True
if 1.e-324: # make sure you don't go beyond computer precision
    print('Any non-zero float value smaller than the computer precision will be set to 0')
if not 1.e-324: # make sure you don't go beyond computer precision
    print('Any non-zero float value smaller than the computer precision will be set to 0')
Any non-zero float value smaller than the computer precision will be set to 0
if 12:
    print('The same rules also hold for integers.')
The same rules also hold for integers.
if "":
    print('An empty string is converted to boolean False')
if not "":
    print('An empty string is converted to boolean False')
An empty string is converted to boolean False
if " ":
    print('A non-empty string is converted to boolean True')
A non-empty string is converted to boolean True
if []:
    print('An empty list is converted to boolean False')
if not []:
    print('An empty list is converted to boolean False')
An empty list is converted to boolean False
if [1]:
    print('A non-empty list is converted to boolean True')
A non-empty list is converted to boolean True
if not {}:
    print('The same rules also hold for sets and dictionaries.')
The same rules also hold for sets and dictionaries.
if {1:2}:
    print('The same rules also hold for sets and dictionaries.')
The same rules also hold for sets and dictionaries.
if not None:
    print('The keyword None is also equivalent to False.')
The keyword None is also equivalent to False.
bool("amir") # You can always get the boolean-conversion of a value or type using Python's built-in function bool().
True


Functions in Python

In Python, like most other programming languages, function is a collection of programming statements that can be executed whenever and wherever requested. Therefore, the definition of function in programming goes far beyond the mathematical definition of function. For example, programming functions can have no input or output.

In Python, the syntax of a function is like the following,

def function_name(argument_1, argument_2, ..., argument_N)
    python_statment_1
    python_statment_2
    ...
    return output


Here, the line beginning with def is referred to as the function header, and the statements inside the function are called the function body. To use a function, it must be first defined like above, and then called where it is needed inside the code.
Example:
Let’s write a Python function that takes in a temperature value in Centigrads and converts it to Fahrenheit.

def c2f(C):
    return (9.0/5)*C + 32

c_temp = 70.7       # The hottest place on Earth, Lut Desert in Iran 
f_temp = c2f(c_temp)

print("""
The hottest place on Earth as of 2005 is in the Lut Desert in Iran at {0} degrees Celsius.
This corresponds to a temerature of {1} degrees Farenheiht!
""".format(c_temp,c2f(c_temp)) )
The hottest place on Earth as of 2005 is in the Lut Desert in Iran at 70.7 degrees Celsius.
This corresponds to a temerature of 159.26 degrees Farenheiht!


Functions with no input arguments

We can define functions that take no input argument, yet do something predefined for us. Consider the following function which gives information about the ECL course, when called.

def get_ecl_info():
    return "Engineering Computation Lab (COE111L) is a new course that is offered by the department of Aerospace Engineering and Engineering Mechanics at the University of Texas at Austin, starting Spring 2017. The overarching goal of the course is to introduce Aerospace undergraduate students with the principles of scientific computing, as well as the applications of numerical methods that the students learn in Engineering Computation (ASE 211K), offered parallel to this course. Towards this goal, Python was chosen as default programming language for the first offering of this course, but it can be switched to other languages such as R, MATLAB, Fortran, C++, etc., depending on the future needs of the Aerospace undergraduate program at UT Austin."

get_ecl_info()
'Engineering Computation Lab (COE111L) is a new course that is offered by the department of Aerospace Engineering and Engineering Mechanics at the University of Texas at Austin, starting Spring 2017. The overarching goal of the course is to introduce Aerospace undergraduate students with the principles of scientific computing, as well as the applications of numerical methods that the students learn in Engineering Computation (ASE 211K), offered parallel to this course. Towards this goal, Python was chosen as default programming language for the first offering of this course, but it can be switched to other languages such as R, MATLAB, Fortran, C++, etc., depending on the future needs of the Aerospace undergraduate program at UT Austin.'


Functions with no output (return value)

We can also modify the above function such that it does not return anything specifically.

def get_ecl_info():
    print( "Engineering Computation Lab (COE111L) is a new course that is offered by the department of Aerospace Engineering and Engineering Mechanics at the University of Texas at Austin, starting Spring 2017. The overarching goal of the course is to introduce Aerospace undergraduate students with the principles of scientific computing, as well as the applications of numerical methods that the students learn in Engineering Computation (ASE 211K), offered parallel to this course. Towards this goal, Python was chosen as default programming language for the first offering of this course, but it can be switched to other languages such as R, MATLAB, Fortran, C++, etc., depending on the future needs of the Aerospace undergraduate program at UT Austin." )

get_ecl_info()
Engineering Computation Lab (COE111L) is a new course that is offered by the department of Aerospace Engineering and Engineering Mechanics at the University of Texas at Austin, starting Spring 2017. The overarching goal of the course is to introduce Aerospace undergraduate students with the principles of scientific computing, as well as the applications of numerical methods that the students learn in Engineering Computation (ASE 211K), offered parallel to this course. Towards this goal, Python was chosen as default programming language for the first offering of this course, but it can be switched to other languages such as R, MATLAB, Fortran, C++, etc., depending on the future needs of the Aerospace undergraduate program at UT Austin.

In such cases, you may have already noticed that we can readily skip the return statement. In reality, in such cases, what happens is that Python interpereter adds an invisible return None statement at the end of the function. None is a special reserved keyword of Python that represents nothing or empty data in Python. It is almost equivalent to the word void in languages like Java, C, and C++.

def get_ecl_info():
    print( "Engineering Computation Lab (COE111L) is a new course that is offered at UT Austin." )
    return None
get_ecl_info()
Engineering Computation Lab (COE111L) is a new course that is offered at UT Austin.

If you set a variable equal to this function, the value of the variable will be None, because the function returns nothing on the output.

variable = get_ecl_info()
type(variable)
print(variable)
NoneType
None

Functions with multiple input arguments

Functions can take almost as many input arguments as we wish. Consider the following,

def power(base,exponent):
    return base**exponent
power(2,5)
32

The order of input arguments both does and does not matter!

Note that in the previous code, calling the function with the wrong order of input parameters, can lead to a catastrophe and wrng output.

power(5,2)
25

However, a really cool feature for function input arguments is that, when calling the function, you can also name the argument variable, and if you name them all, then the order by which the arguments appera becomes irrelevant.

power(exponent=5,base=2)
32

Functions with multiple output (return values)

Python funcitons can return more than one value. For this purpose, tuple variable types become a handy tool. Recall that making a tuple is as simple as writing a sequence of comma separated values/variable, like the following.

mytuple = 1, 2
mytuple
type(mytuple)
(1, 2)
tuple

Now, if you need to write a function that has multiple return value, you can simply return them all in one sequence of comma separated values/variables. For example, suppose a function takes in two numbers, and then outputs the quotient (the result of integer division) and the remainder of the integer division. An example such function would be like the following,

def get_quotient_remainder(dividend,divisor):
    return divmod(dividend,divisor)

get_quotient_remainder(11,3)
(3, 2)
type(get_quotient_remainder(11,3))  # By default, the output is a tuple
tuple

By default, the output of this function is a tuple, since we are returning a tuple in the function. But we could also return the output as a list, or any other appropriate format we wish.

def get_quotient_remainder(dividend,divisor):
    return list(divmod(dividend,divisor))   # convert the output to list before passing it to main program

type(get_quotient_remainder(11,3))
list

You can also save the output in a variable as well,

def get_quotient_remainder(dividend,divisor):
    return divmod(dividend,divisor)

result = get_quotient_remainder(dividend=11,divisor=3) # You can also name the input variables to make sure you assign them in the correct order.
print(result)
(3, 2)

or save the individual results in separate variables, like,

quotient, remainder = get_quotient_remainder(dividend=11,divisor=3)
print(quotient, remainder)
3 2

or save it in the form of a tuple or list,

[quotient, remainder] = get_quotient_remainder(dividend=11,divisor=3)
print(quotient, remainder)
3 2

Functions with optional input arguments

Like many other high-level programming languages, Python allows you to have optional arguments in your input, which you can drop when calling the function. However, an optional argument must have a preassigned value in the function, otherwise dropping the variable at the time of function call will lead to a runtime error.

def get_quotient_remainder(dividend,divisor=10,message="This is the default message."):
    print( "divmod({},{}) = ".format(divmod(dividend,divisor)) )
    print( message )
    return divmod(dividend,divisor)

result = get_quotient_remainder(dividend=11,divisor=3) # the optional input argument 'message' is set to its default value.
divmod(11,3) = (3, 2)
This is the default message.

The above function, default the value of optional message argument to "This is the default message." since it is not given at the time of calling the function. The function’s optional input arguments whose values are initialized to a default value are more famously known in Python as keyword arguments.

ATTENTION:

Note that when the optional arguments are named and assigned a value at the time of function call, then their order of appearance in the function call does not matter. However, keep in mind that the **ordinary** or **positional** arguments should all appear in order and first, before the keyword arguments appear. However, the keyword arguments can appear in any order one may wish.


For the above example function, the following function calls would be valid,

get_quotient_remainder(dividend=11)
get_quotient_remainder(11,divisor=3)


and the following would be invalid,

get_quotient_remainder(divisor=3,11,message="A new message.")   # This is invalid
  File "<ipython-input-36-27658299aa76>", line 1
    get_quotient_remainder(divisor=3,11,message="A new message.")   # This is invalid, the order is incorrect.
                                    ^
SyntaxError: positional argument follows keyword argument
get_quotient_remainder(11,divisor=3,"A new message.")   # Also invalid, all arguments after the first named argument must appear with name as well.
  File "<ipython-input-37-1643cc8d6910>", line 1
    get_quotient_remainder(11,divisor=3,"A new message.")
                                       ^
SyntaxError: positional argument follows keyword argument
ATTENTION:

Note that the keyword arguments must be always listed after the positional arguments in the function definition. Note also, as in the above examples, that the sequence of input arguments **at the time of function call** does not matter, so long as the names of all positional and keyword arguments are provided in the function call.

Note also, that the number input arguments at the time of call must be exactly the same as the number of arguments in the function definition.

get_quotient_remainder(11,3,"A new message.",)  # THis works even though, there is an extra comma at the end of the arguments of the function call.
divmod(11,3) = (3, 2)
A new message.

(3, 2)


Local and global variables in functions

Variables that are defined insde of a function, are by default invisible outside the function scope. For example, let’s consider Let us reconsider the original function defined at the beginning of the lecture, which takes in a temperature value in Centigrads and converts it to Fahrenheit.

def c2f(C):
    converted_value = (9.0/5)*C + 32
    return converted_value


Now, if the variable converted_value is called outside the function, it will result in a syntax error since it is undefined outside the function scope.

c2f(70)
158.0
converted_value
NameError                                 Traceback (most recent call last)
<ipython-input-42-1aa75d9b79c4> in <module>()
----> 1 converted_value

NameError: name 'converted_value' is not defined
Local variables are created inside a function and destroyed when the program control goes back to the main code, outside the function.

Now suppose we had the following script,

def c2f(C):
    converted_value = (9.0/5)*C + 32
    print('Value of C inside function: {}'.format(C))
    return converted_value

C = 70
c2f(50)
print('Value of C outside function: {}'.format(C))
Value of C inside function: 50
Value of C outside function: 70

Clearly, the two values are not the same, even though the variable names are the same. But, if you really want to access the global variable C inside of the function, then you can use Python’s built-in function globals() which returns a dictionary of all global variables in the main program, and then use the keyword 'C' to get its value inside the function.

type(globals())
dict
globals()['C']
70

Now, the same function as above, but with the global variable value would give you,

def c2f(C):
    converted_value = (9.0/5)*globals()['C'] + 32
    print('Value of C inside function: {}'.format(globals()['C']))
    return converted_value

C = 70
c2f(50)
print('Value of C outside function: {}'.format(C))
Value of C inside function: 70
Value of C outside function: 70
As a general rule, when there are several variables with the same name, Python interpreter first tries to look up the variable name among the local variables, then there is a search among global variables, and finally among built-in Python functions.

In order to declare a variable inside the function global, use the keyword global as in the following,

a = 20; b = -2.5 # global variables
def f1(x):
    a = 21 # this is a new local variable
    return a*x + b

print (a) # yields 20

def f2(x):
    global a
    a = 21 # the global a is changed
    return a*x + b

f1(3); print (a) # 20 is printed
f2(3); print (a) # 21 is printed
20
20
21

Note that in function f1, $a = 21$ creates a local variable a. One may think the global a has changed, but it does not happen. However, in the second function f2, the globally declared variable is assigned a new value and therefore, the global value of a outside the function also changes. Test this script yourself and see what you get.

Be careful with using global variables inside your functions, because if you do not define them prior to using the function, then you get a runtime error.


Here is an example of the error,

def f2(x):
    global a    # a must be defined outside the function prior to function call
    return a*x + b

f2(3)
NameError                                 Traceback (most recent call last)
<ipython-input-55-ea6f63a0f6d6> in <module>()
    3     return a*x + b
    4 
----> 5 f2(3)

<ipython-input-55-ea6f63a0f6d6> in f2(x)
    1 def f2(x):
    2     global a    # a must be defined outside the function prior to function call
----> 3     return a*x + b
    4 
    5 f2(3)

NameError: name 'a' is not defined

Avoid function side-effects

A function in which the value of a global variable is changed while the global variable is not the intended output of the function, is called a function with side-effects. Here is an example,

def f2(x):
    global a
    a = 21 # the global a is changed
    return a*x + b


In general, any lasting effect that occurs in a function, but not through its return value, is called a side effect. There are three ways to have side effects:

  • Changing the value of a mutable object.
  • Changing the binding (the storage space) of a global variable.
  • Printing out a value. This doesn’t change any objects or variable bindings, but it does have a potential lasting effect outside the function execution, because a person might see the output and be influenced by it.
In general, avoid defining functions that have side-effects. In large codes and projects, side-effects can create complex semantic errors and become a hurdle for optimization and code debugging.


Python’s built-in functions

Python has a number of built-in functions, which can be handy. Here is a list of Python’s built-in functions along with a description of what they do.

function docstring

There is a convention in Python to insert a documentation string right after the def line of the function definition (the function header). The documentation string, known as a doc string or docstring, should contain a short description of the purpose of the function and explain what the different arguments and return values are. Doc strings are usually enclosed in triple double quotes """, which allow the string to span several lines.

def c2f(C):
    """
    This function converts Celsius degrees (C) to Fahrenheit.
    Uses global variable C.
    """
    converted_value = (9.0/5)*globals()['C'] + 32
    print('Value of C inside function: {}'.format(globals()['C']))
    return converted_value


The docstring is then stored in __doc__ attribute of the function, and can be called like the following,

c2f.__doc__
'\n    This function converts Celsius degrees (C) to Fahrenheit.\n    Uses global variable C.\n    '

If you’d like to get the formatted docstring, use Python’s help() function,

help(c2f)
Help on function c2f in module __main__:

c2f(C)
    This function converts Celsius degrees (C) to Fahrenheit.
    Uses global variable C.

One can also use " in place of """, although less conventional. But then for multiple lines of docstring, one has to use line continuation.

def c2f(C):
    "\
    This function converts Celsius degrees (C) to Fahrenheit.\
    Uses global variable C.\
    "
    converted_value = (9.0/5)*globals()['C'] + 32
    print('Value of C inside function: {}'.format(globals()['C']))
    return converted_value
Note that the docstring must appear before any statement in the function body.

Functions as input arguments to functions

In happens frequently in scientific programming, that a function needs to use another arbitrary function provided by the user to perform some specific tasks with it inside the function. For example, programs doing calculus frequently need to have functions as arguments in functions. For example,

  • a Python function that finds the root of a mathematical function, given as input argument.
  • a Python function that differentiates of a mathematical function, given as input argument.
  • a Python function that integrates a mathematical function, given as input argument.
  • a Python function that solves a mathematical differential equation, given as input argument.

In such cases, Python functions need to have the mathematical function as an input argument with some name(e.g., func). Like Fortran, this is straightforward in Python and hardly needs any explanation, but in most other languages special constructions must be used for transferring a function to another function as argument. For example, suppose we want to compute numerically the second-derivative of a user given mathematical function,

def diff2nd(func, x, h=1E-6):
    r = (func(x-h) - 2*func(x) + func(x+h))/float(h*h)
    return r

The func input argument is like any other argument, i.e., a name for an object, here a function object that we can call as we normally call functions. An an application, suppose we want to calculate the second derivative of a quadratic function $g(x)$ of the following form at $x=2$,

def diff2nd(func, x, h=1E-6):
    r = (func(x-h) - 2*func(x) + func(x+h))/float(h*h)
    return r

def g(x):
    return x**2 + 4.0*x + 1.0

x = 2
diff2nd_g = diff2nd(g, x)
print ( "g’’(x=%f)=%f" % (x, diff2nd_g) )
g’’(x=2.000000)=2.001954

Function composition

The ability to call one function from within another function is called composition. Suppose we have a function distance that calculates the distance between two points on a 2D plane. and another function that takes in a value for radius, and then calculates the corresponding area for that radius.

def distance(x1, y1, x2, y2):
    from math import sqrt
    return sqrt( (x2-x1)**2 + (y2-y1)**2 )

def area(x1, y1, x2, y2):
    from math import pi
    return pi*distance(x1, y1, x2, y2)**2

area(0.0,0.0,0.0,1.0)
3.141592653589793

Recursive functions

Now, note that the function being called inside the other, does not necessarily have to be a different function. It could be the same function calling itself repeatedly, until a condition is met (otherwise this would be an endless function call to itself for eternity). For example, a function that would calculate the factorial of an input integer would be like the following,

def factorial(n):
    if isinstance(n,float):
        print('The input number {} is not an integer!'.format(n))
        return None
    if n==0:
        return 1
    elif n==1:
        return n
    else:
        return n*factorial(n-1)

factorial(4)
factorial(4.5)
24
The input number 4.5 is not an integer!

Lambda functions

There is a quick one-line construction of functions that is often convenient to make Python code compact. For example, recall how we defined g(x) in the example above. Here is a compact version of it,

g = lambda x: x**2 + 4.0*x + 1.0


This code is equivalent to the original code that wrote before,

def g(x):
    return x**2 + 4.0*x + 1.0


In general, a function of the form,

def g(arg1, arg2, arg3, ...):
    return expression


can be converted to the compact form,

g = lambda arg1, arg2, arg3, ...: expression


Lambda functions are very useful for defining simple functions in the argument list that is passed to another function. For example, recall our diff2nd function that we defined in the above,

def diff2nd(func, x, h=1E-6):
    r = (func(x-h) - 2*func(x) + func(x+h))/float(h*h)
    return r

def g(x):
    return x**2 + 4.0*x + 1.0

x = 2
diff2nd_g = diff2nd(g, x)
print ( "g’’(x=%f)=%f" % (x, diff2nd_g) )
g’’(x=2.000000)=2.001954

Now instead of defining g(x) separately when calling diff2nd function, we can use the following compact form,

def diff2nd(func, x, h=1E-6):
    r = (func(x-h) - 2*func(x) + func(x+h))/float(h*h)
    return r

x = 2
print ( "g’’(x=%f)=%f" % (x, diff2nd(lambda x: x**2 + 4.0*x + 1.0, x) ) )
g’’(x=2.000000)=2.001954

Lambda functions may also take keyword arguments. For example,

d2 = diff2nd(lambda t, A=1, a=0.5: -a*2*t*A*exp(-a*t**2), 1.2)


This format is particularly useful, if the lambda function contains a constant that is repeatedly used in the expression of the function, the value of which may need to be updated in future runs of the code, or later on in the same code.



Comments