Higher Order Functions #
Designing Functions #
Describing Functions #
Aspect | Example |
---|---|
The domain of a function is the expected range of inputs (similarly to a domain in mathematics) |
x is a string. |
The range of a function is the set of output values that could be returned |
Function square returns a non-negative number. |
The behavior of a (pure) function is the relationship between the input and the output |
Function square returns the square of an input x . |
Don’t Repeat Yourself! #
When making a function, give each function exactly one job, but allow it to be flexible to apply to many related situations. Doing this allows you to remove redundant code and make your code clearer to read, and easier to write.
Generalization #
If you see a common structure, you can refactor your code to move the redundant code to a function and call that instead.
For example, if we take this code block below:
def print_ben_if_positive(n):
if n > 0:
print("ben")
def print_ben_if_positive(n):
if n > 0:
print("tao")
We can see that the if
statement is duplicated, so we can take that logic to another function instead, removing redundant code.
def positive_print(number, message):
if number > 0:
print(message)
positive_print(n, "ben")
positive_print(n, "tao")
Higher-order Functions #
A higher-order function is a function that takes another function as an argument, or returns a function as its result.
Functions as Arguments #
A function can take another function as an argument.
from operator import add, sub
def function_in_function(adding_function, x, y):
return adding_function(x, y)
function_in_function(add, 3, 2) # >>> 5
function_in_function(sub, 3, 2) # >>> 1
In the example above, we passed the add
and sub
functions to the function function_in_function
. This is an example of a higher order function.
Note that in the call functions themselves,add
andsub
were called rather thanadd()
andsub()
. This is because of how call functions are evaluated. Runningadd()
directly will make the Python interpreter try to evaluate the function itself with no parameters, which would not work, while simply typingadd
will allowfunction_in_function
to evaluateadd()
with the proper parameters.
Functions as Return Values #
Another way to have a higher order function is to return a function.
Functions created in other functions are bound to names in their local frame, allowing for cleaner naming when creating function calls. For example:
def make_adder(magnitude):
def adder(n):
return n + magnitude
return adder
add_two = make_adder(2)
add_three = make_adder(3)
print(add_two(2), add_three(2)) # >>> 4 5
While the example above may not have much practical use, it shows the process of returning a function within a function, making this another higher order function. Take your time to digest how the above two examples work. This will be harder to conceptualize at the beginning.
Call Expressions as operator expressions #
You can run a function like make_adder(2)(3)
which will return 5
. Why is that? Let’s look at how the call function order works.
make_adder(2)
is first evaluated, which returns a functionadder
similarly to the function above.- Due to
adder
being returned from the operator, the final statement will end up beingadder(3)
, leading to5
being the final output
Lambda Expressions #
Lambda expressions pretty much work the same way as regular functions, other than the fact that these functions are anonymous - they do not have a name assigned to it.
The syntax looks like the following:
lambda <parameters>: <expression>
For instance:
double = lambda x: y*2
double(3) # >>> 6
While this may not seem very useful in this context, there are quite a few uses for it. The main idea is that lambda
functions are better suited for functions that you want to access in the short term, or only access once.
A lambda function does not contain any statements at all, including if
statements and return
statements.
For instance, instead of doing
from operator import add
def square(x):
return x**2
add(2, square(2))
You can do the following:
from operator import add
add(2, lambda x: x**2)
Conditional Expressions #
lambda
functions not allowing statements can be quite limiting, but that is where conditional expressions come in.
A conditional expression has a form that almost reads like English:
<do this (true)> if <condition> else <do this (false)>
This may be strange if you’re used to ternary operators in other languages (for example JavaScript) as the order of reading the Python statement may be a bit strange.
However, the operation order is the following:
- Check the truthiness of
<condition>
- If true, evaluate
<do this (true)>
- Else, evaluate
<do this (false)>
In conjunction with lambda
functions, you can do the following:
lambda x: x if x > 0 else 0
The code block above returns the input if it is positive, else returns 0
.