There are three more concepts tied to functions that we must understand before moving on. They are each demonstrated in the following exercise:
# more_functions.py
# params and returns
def quadratic_formula(a, b, c):
"""
Calculates the roots of f(x) given a, b, c where
f(x) = a * x**2 + b * x + c
Returns the solution as a tuple.
Returns None if the solutions are imaginary
"""
discriminant = b**2 - 4*a*c
if discriminant < 0:
return None
root1 = (-b - discriminant**0.5) / 2 / a
root2 = (-b + discriminant**0.5) / 2 / a
return root1, root2
imaginary_solution = quadratic_formula(1, 2, 3)
real_solution = quadratic_formula(1, -2, -3)
print("Imaginary Solution is:", imaginary_solution)
print("Real Solution is:", real_solution)
# default params
def print_a_number(number=1):
print(f"Your number is: {number}")
print_a_number(3)
print_a_number()
# recursion
def factorial(number):
"""
Returns the factorial of a number
It first turns it into an integer
before calculating its factorial
"""
number = abs(int(number))
if number <= 0:
return 1
return number * factorial(number - 1)
print("4! =", factorial(4))
print("2.5! =", factorial(2.5))
$ more_functions.py
Imaginary Solution is: None
Real Solution is: (-1.0, 3.0)
Your number is: 3
Your number is: 1
4! = 24
2.5! = 2
$
Parameters are related to arguments. Many times these two terms are used interchangeably. To clarify, a parameter is the variable name that is used in the definition of a function. Whereas, an argument is what actually gets passed into the function call as seen here:
def function_name(parameter):
parameter += 3
print(parameter)
# now we call the function
function_name(argument)
Following this logic, the argument replaces the parameter while the function code executes. You have worked with arguments since the first Python program you ran. The simple statement print("Hello World!") is just you calling the Python function named print and passing in the string "Hello World!" as an argument. Somewhere in the Python core code, there is a print function defined in a similar manner that executes all the details of printing text out to the command line.
We saw in line 29 the following line of code: def print_a_number(number=1):. This function definition supplied a default value for the number parameter. Any function parameter may have a default value. The effect of this definition is that if no argument is passed that corresponds to that parameter the value for the parameter will be assumed and inserted into the function. However, if the corresponding argument is given then it will override the default value. You may mix and match required and optional parameters like this but you must put the optional parameters with default values after the required “positional” parameters.
You may have found that functions in Python have some practical similarities with mathematical functions. Just as a mathematical function f(x) will evaluate to a number when a value of x is supplied, a Python function that returns a value will evaluate to whatever value is returned. There are a few things to understand about the return statement:
Any time a return statement is reached in Python, the expression after the word return is evaluated and the function call takes on the value of that evaluation. Therefore, if you have a return statement such as return 2*3 + 4 the function will evaluate to 10. This is shown below:
def get_10():
return 2*3 + 4
x = get_10()
# x now has a value of 10
A function always ends after a return statement regardless of where the statement occurs in the code. Therefore, return is often used to end a function code early if necessary. We saw this on line 13 - 14 in more_functions.py where it says:
if discriminant < 0:
return None
This will end the function if it is found that the solution will be imaginary.
Functions that have no return statement return a special data type called None by default. The None data type is basically Python’s way of saying this data is nothing or void. Therefore when we explicit returned None we could have also just written return . Both cases would be saying, if the solution will be imaginary return nothing.
It is possible to return multiple values (as seen in more_functions.py in the quadratic_formula function). As you saw above, they will simply be returned as a tuple.
Return statements are crucial for effective functions and it is a good idea to experiment with what you can return in a function.
Recursion is the idea that a function or some process can reference or call itself. A great explanation of recursion as a concept can be found on Wikipedia. We saw this with the factorial example. In mathematics, a factorial is a number being multiplied by all the positive integers below it and is symbolized by an exclamation mark. So the factorial of 4 = 4! = 4 * 3 * 2 * 1. In this case we could do a loop to accomplish this but to demonstrate this principle we can do it with recursion. We set up a base case to end the recursion:
if number <= 0:
return 1
Then we have the function call itself with a small adjustment:
return number * factorial(number - 1)
And the function starts over again with the updated argument. This continues until the base case has been reached.
Recursion is a powerful tool and, when used judiciously, can easily do things that would otherwise take ridiculous amounts of code to implement without it. However, recursion can get you in to a lot of trouble if you are not careful with it. For example, it is easy to have a base case that will never be reached (imagine if the code read ` return number * factorial(number + 1)`) or forget to put in a base case. If this happens then you can make an infinite recursion that will break your program. (Python thankfully has a feature which detects recursion and limits the depth to which a program can recurse.)