We have already encountered several built-in functions. Some of these are part of the core Python programming language (e.g., 'print', 'input', etc), while others are found in special modules/libraries (e.g., the math module that contains the inverse trig functions, logarithmic functions, etc). A module is a file that contains a collection of related functions. Before you can use a module, you have to import it:
import math
root = math.sqrt(2) # the variable 'root' is assigned the output
# of the function 'sqrt' from the 'math' module
print('Square root of 2: ', format(root, '.4f')) # display result on the screen
In this unit you will learn how to create your own functions (these are known as user-defined functions).
In the context of programming a function is a named sequence of statements that performs a computation. Functions in Python need three things: a name, a block of code, and (optionally) some variables that hold what values you send the function (we call these parameters). Once you have defined your function, you can then "call" the function by name; we refer to such statements as function calls. Here's the design pattern for a function:
def <function_name>(parameter1, parameter2, ....):
<body> # any number of statements
# your definition ends when you stop indenting
To use a function you must call it. You do this by using the function name, followed by a pair of parentheses. Here is a very basic example:
# very basic function:
def hello(): # this function has NO parameters
print("Hello! How are you?") # body of function
hello() # call to the function
We can make this more interesting (sort of...):
# slightly more complex function:
def happy(): # this has no parameters either
print("What\'s your name?") # ... body ...
name = input() # ... body ...
print("Happy Birthday,", name, "!") # ... body ...
happy() # call to the function
We can make the 'name' variable an argument for our last function -- like so:
def happy2(name):
print("Happy Birthday,", name, "!")
happy2('Jacqueline') # a call to our function...
happy2('Igor') # another one...
happy2('Oscar') # and yet another one
When there are no parameters, we still have to include the parentheses in the function calls. If you don't add them, Python does not know that you are actually calling the function. Your program won't crash, but you'll get some odd-looking output. Here is what happens when we call the 'happy' function above without parentheses:
happy # call to the 'happy' function
If you see something like this, you probably made a syntax mistake in your call to a function.
# a function that calculates the average of 3 numbers:
def myAverage(a, b, c): # this function takes 3 parameters
avg = (a + b + c)/3
print("The average is: ", format(avg,'.3f'))
# function definition ends here
a = float(input("a = ")) # some separate lines of code...
b = float(input("b = "))
c = float(input("c = "))
myAverage(a, b, c) # call the function
The definition of the function has to appear before it is used. In the above example, if you put the last line at the top of your file, you'll get an error. Since we have already called the function 'myAverage', this is still active in the workspace, so if you try to do what I've just said you'll probably notice nothing. You should reset the workspace (which is like wiping clean a whiteboard).
# the next line is used for resetting the workspace:
%reset -f
a = float(input("a = ")) # some separate lines of code...
b = float(input("b = "))
c = float(input("c = "))
myAverage(a, b, c) # call to the function DEFINED BELOW
def myAverage(a, b, c): # this function takes 3 parameters
avg = (a + b + c)/3
print("The average is: ", format(avg,'.3f'))
# function definition ends here
In order to ensure that a function is defined before its first use, you have to know the order in which the statements are executed (known as the flow of execution). As it was mentioned previously, the execution of a Python program begins at the first statement of the program, and statements are executed sequentially, in order from top to bottom.
Function definitions do not alter the flow of execution of the program. However, and this is very important, the statements inside the function are NOT executed UNTIL the function is CALLED.
When Python encounters a call to a function, the flow of execution jumps to the body of the function, executes all statements there, and then comes back to pick up where it left off.
In the above example, Python encountered a call to the function 'myAverage'. At that stage, it was "unaware" what that name referred to, and the message it throws back on the screen informs you of that. If the code crashes, the execution is terminated at that point. This means, whatever comes after that point remains unexecuted.
It is good practice to start your computer program with various function definitions that you might need in your code. The order in which you write them is not important, as long as there are no interdependencies among your functions. The last function should be a 'main()' function where you actually put all the other lines of code, including the calls to your own-defined functions. Finally, you should remember to call your 'main()' function. Here's this structure illustrated for the above example:
"""
illustrates how you should write your code
"""
#
# at the top of the code you usually place the
# various import stamenets (e.g., 'import math', etc)
def myAverage(a, b, c): # ... followed by function defs.
avg = (a + b + c)/3
print("The average is: ", format(avg,'.3f'))
# maybe you have more than just one function....
def main(): # this is the main block of statements
a = float(input("a = ")) # written as a function
b = float(input("b = "))
c = float(input("c = "))
myAverage(a, b, c)
# here's the end of our main()
main() # don't forget to call main(), so that
# what's inside gets a chance of being executed....
The previous examples already illustrate the basics of using functions with parameters. In the last example above we had a function with 3 parameters.
If you pass in more than one value, Python will save the first value in the first parameter, the second value in the second parameter, etc. If you have a function with 10 parameters it might be difficult to remember in what order you defined the parameters. Python allows you to specify which value goes with what parameter. This is done by using the parameter's name in the function call. Here's a modification of the above example:
# another way to pass values to parameters
# in a Python function:
def myAverage(a, b, c):
avg = (a + b + c)/3
print("The average is: ", format(avg,'.3f'))
# maybe you have more than just one function....
def main():
myAverage(a=3, b=4, c=5) # use the parameter's name to pass values....
# here's the end of our main()
myAverage(b=4, a=3, c=5) # ...in THIS CASE the order of the parameters
# is irrelevant; this statement is the same as
# the one immediately above
main()
It is possible to set default values to the parameters of a function. This is a particularly useful feature, because if you set up default values for a function's parameters, then even if the user forgets to provide a value, the function will still execute (Note: this might not be want you want in certain cases; don't abuse this feature!).
Let's say that we want to find the maximum height reached by a projectile launched with an initial speed $v_0$ and a launch angle $\theta$. The formula is
$$ H = \dfrac{v_0^2\sin^2\theta}{2g} $$
where $g$ is the gravitational acceleration. Let's say that we want to compare the maximum heights reached by such a projectile on different celestial bodies. The only thing that we need to change is $g$. Here's the code:
import math
# g = 9.81998 ~ 9.8 Earth
# g = 1.625 Moon
def max_height(v0, a0, g=9.8):
tmp = 0.5*(v0**2)*(math.sin(a0)**2)/g
return tmp
def main():
v0 = 6.5 # initial speed
a0 = math.pi/3 # initial launch angle (60 degrees)
h1 = max_height(v0, a0) # Python will use the default value for 'g'
print('Max height reached: ', h1)
h2 = max_height(v0, a0, 1.625) # I am using 'g' for the Moon
print('Max height is: ', h2) # about 6 times larger than h1
main()
One of the rules of setting default values for the parameters of a function is that you define them last in the function definition. All the parameters that must be defined in a call to a function will go first; the definition for the defaults will always follow those.
Default values for function parameters are optional, so make sure you do not implement this feature in every function you write...
Another handy feature of Python functions concerns the flexibility of the number of parameters in function definitions. Basically, user-defined functions can take an arbitrary number of parameters. This requires some special syntax you should be aware of.
# the function in this example calculates the
# sum of the values the user provides;
# there is no limit on the number of values
def add_vals(first, second, *args):
total = first + second
if not args:
print('Only two numbers provided')
print('Total is: ', total)
else:
for arg in args:
total = total + arg
print('Total is:', total)
def main():
add_vals(1,2,3,4,5,6,7,8) # here is one run....
print('----------------------')
add_vals(23, 45) # ... and here's another
main()
In the above code I've used the fact that an empty tuple evaluates to 'False'. The additional parameters that the user might specify, in addition to the first two, will be stored in a tuple. If this tuple is empty, then the statement 'if args' will evaluate to 'False'; but the statement 'if not args' will evaluate to 'True'. This means that the first part of the if-statement will be executed. If the tuple contains some elements, then Python executes the second part of the if-statement. This involves a definite loop that adds the values stored in the tuple to the variable 'total' (which already stores the sum of the values in the first two parameters that were passed to the function).
In mathematical programming it is often the case that functions must return numerical values that are supposed to be used in other parts of the program. If you look carefully at the example with calculating the average of the three numbers, the result was displayed on the screen from within the function, but the actual value was not available outside the function 'myAverage'. This type of situation is not very satisfactory. Fortunately, Python allows us to do this very easily by using the keyword return. Here's a simple example that returns the sums all the values stored in a list:
def get_total(items):
total = 0
for item in items:
total = total + item
return total # note the use of the keyword 'return'
def main():
my_list = [1,2,3,4,5,6] # user input (can change this)
my_total = get_total(my_list) # call to the above function
print('The sum of the values stored in \'my_list\' :', my_total)
main() # remember, this function must be called
A function can return multiple values. All that is required is to use the return statement followed by the variables whose values we want returned, separated by commas. Check this out:
def cube_and_square(number):
square = number**2
cube = number**3
return square, cube # multiple values will be returned
# we are actualy returning a tuple...
def main():
res_1 = cube_and_square(2) # res_1 will store a tuple....
print('Output: ', res_1)
res_2, res_3 = cube_and_square(3) # this style of assignment allows the user
# to catch the values in separate variables;
# it avoids the hassle of tuple manipulations later on....
print("Square is: ", res_2)
print("Cube is: ", res_3)
main()
Note that with the second style of retrieving the values returned by a function, you'll have to know HOW MANY items you'll be getting back from the call to your function. If you try to assign too many or too few variables, Python will give you an error.
You don't always have to follow a 'return' statement with some variables. A 'return' statement can also be used to force the flow of execution back to the main program, rather than run the rest of the code in your function -- like in this example:
def check_array(userList):
if len(userList) != 6:
print('Input list has an invalid '
'number of elements')
return # execution returns to main() after this...
if len(userList != 6):
print("I never reach this point....")
def main():
aa = [1,2,3,4,5,6,7,8] # user input
check_array(aa) # call to the above function
main()
Because the list 'aa' has a length of 8, the function displays an error message and returns to the main program rather than continue to check the second conditional statement (which will clearly evaluate to 'True' in this case).
Typically, the body of a function will contain statements involving any syntax supported by Python; these include conditional statements, for-loops, assignments, various mathematical operations, and so on. It is quite common to have to create new variables inside the body of a function; maybe in order to set up a loop, or maybe to keep count of some iterations, etc. When you declare a variable inside a function, it only exists within that function. Such variables belong to the local scope of the function, and are referred to as local variables.
# illustrating LOCAL scope:
def myfunc():
x, y = 10, 20 # local variables
print("Passing through \'myfunc\' :", x+y)
def main():
print("Passing through \'main\' ")
z = 60 # this one is local, too
print('z is: ', z)
myfunc() # this statement calls the first function
main()
In this example, x and y belong to the local scope, and are known as local variables. The main() function is completely "oblivious" to their existence. Similarly, z is "invisible" to the user-defined function myfunc().
A variable created in the main body of the Python code belongs to global scope. Such global variables are available within any scope, global and local.
# illustrating GLOBAL (and LOCAL) scope
g = 9.8 # global variable
def weight_calc(m):
weight = m*g # 'weight' is a local variable
print('Weight:', weight, 'N') # display result
weight_calc(70) # call function
print('g is: ', g)
print('Weight is: ', weight) # this will give an error
# because 'weight' has local scope
We have covered variables defined inside and outside a function. How about the parameters of a function? (these are the variables that appear on the same line as the def keyword -- see the earlier discussion). In the example immediately above, 'm' is the parameter of the user-defined function 'weight_calc'.
When you send values to a function (i.e., provide values for the parameters in a function call), you're usually sending copies of them. Even when you use variables to pass values to a function, and the variables happened to be named exactly the same as the parameters of your function, changes you make to the values of the parameters inside the function, are not going to affect the original external variables.
Here's an example of a function that adds together two numbers, but not before each gets incremented by a fixed value:
def add_numbers(number1, number2):
number1 = number1 + 3
number2 = number2 + 3
sum = number1 + number2
print(sum)
# let's try this function out:
x, y = 6, 8
add_numbers(x,y)
# check to see if the values stored
# in x and y have changed:
print(x,y) # the answer is negative,
# the original values are the same
The reason x and y have not changed (even though the function does modify the values passed to its parameters), is because it was NOT x and y that were sent to the function, but only some copies of them.
Unfortunately, this is not always the case. Certain data types are considered as mutable, which means that something like the above function will modify the original variables whose values are passed to its parameters. Floating-point numbers, integers, and strings are immutable, which means that this can't happen to them, but lists are mutable. Extra care must be taken when lists are used as parameters in a function.
These are named after the Greek letter $\lambda$, and are also known as anonymous functions. The terminology is justified because such functions do not actually have a name; they only have code to execute associated with them. As a rule, lambda functions are mainly used for very trivial tasks (e.g., multiplying some numbers, etc).
The design pattern for lambda functions is included below:
<name> = lambda <variables>: <simple code>
Note that you cannot place statements inside a lambda expression. This means that you can't have an embedded 'if-statement' or a variable declaration in your lambda function. Let's see some examples in action:
double = lambda x: x*2
This statement creates a lambda function called 'double', which takes a single argument (parameter) and doubles it. You can use this function like this:
double(2)
double(double(2)) # function composition: f(f(x))
print('Five x Two is: ', double(5)) # you can pass it as an argument
# to another function
You can have lambda functions with multiple arguments:
combine = lambda x, y, z: x*y*z # multiply together 3 numbers
print(combine(1,2,3))
print(combine(5,5,5))
poly2 = lambda x: 2*(x**2) - 4*x + 1 # quadratic = polynomial of degree 2
for x in range(-10,10):
print(format(x,'4d'),'|', format(poly2(x),'4d'))
This last example shows us that the quadratic defined inside the lambda function 'poly2' has a root between 0 and 1, and another root between 1 and 2. You can use these lambda functions to experiment with simple numerical mathematics (like in this example).
REFERENCE:
T. Gaddis, Starting out with Python (Fourth Edition), Pearson Education Ltd., 2018