Python functions are a block of code that executes particular operations when they’re requested to run, which we term calling a function. Most of the time, we declare and call a function using the following syntax:
>>> # define a function using the def keyword
>>> def add_up(num1, num2):
... print("called the add_up function")
... sum_n = num1+num2
... return sum_n
...
>>> sum0 = add_up(3, 5)
called the add_up function
>>> print(sum0)
8
In the code above, we declare a function called add_up
using the def
keyword. This function takes two numbers (num1
and num2
) as the input arguments, calculates their sum as the defined operation, and returns this value as the function’s output. Pretty straightforward, right?
Besides these regular functions, there are other kinds of functions in Python that can benefit our development. Let’s review them in this article.
Lambda functions are small anonymous one-line functions. Sometimes, we can simply refer to them as lambdas. Lambdas have the following syntax:
lambda arguments: expression
We declare a lambda function using the lambda
keyword. Following this keyword, we specify the list of arguments, the number of which can vary from zero to multiple. We then specify the operations that we want to perform, which is known as the expression for the lambda function.
As you can see, lambdas have very concise syntax and thus they are most suitable for scenarios where a brief, single-time-use function is needed. Let’s consider the following example. The sorted()
function is to sort an iterable based on the key function specified by the key
argument:
>>> # define a list of tuples
>>> records = [(1, 'John'), (2, 'Aaron'), (5, 'Ian')]
>>> # sort with a lambda
>>> sorted(records, key=lambda x: len(x[1]))
[(5, 'Ian'), (1, 'John'), (2, 'Aaron')]
In the code above, we first define a list to store the records of some students with their student IDs and names. We then sort them with a lambda function, which sorts the list by the length of the student’s name.
For information about sorting using lambda functions, you can refer to my previous article. It’s also important to know that lambdas can be misused.
Closures are nested functions that capture non-local variables of the outer functions. I don’t think that it’s easy to understand closures conceptually. A thorough understanding requires in-depth knowledge of scopes and functions being first-class objects in Python. For the purpose of this article, let’s have an overall idea of closures by going over a tangible example:
>>> # create a closure
>>> def make_multiplier(coefficient):
... product = 1
...
... def multiplier():
... nonlocal product
... product *= coefficient
... return product
...
... return multiplier
...
In the code above, we define a function called make_multiplier
. Because it contains the other function multiplier
, we can call make_multiplier
an outer function and call multiplier
a nested function. The outer function returns the nested function as its return value. Importantly, the nested function uses and modifies a non-local variable (i.e. product
) defined within the scope of the outer function. To sum up, there are three key elements to creating closures in Python:
How can we use closures? Let’s see the trivial example below:
>>> multipler3 = make_multiplier(3)
>>> multipler3()
3
>>> multipler3()
9
>>> multipler3()
27
>>> multipler3.__code__.co_freevars
('coefficient', 'product')
>>> multipler3.__closure__[1].cell_contents
27
We then declare a closure called multiplier3
. Every time we call this closure, the product is multiplied by three. In other words, the closure “remembers” the state of the product after its last use. Related concepts include variable binding and value capturing. We can check the pertinent information by calling __code__.co_freevars
and __closure__[1].cell_contents
.
Decorators are functions that extend the behavior of other functions without explicitly modifying them. In essence, decorators are a kind of higher-order function, which is defined as a function that either takes other functions as input or returns other functions as output. Let’s get a real feel for decorators through the example below:
>>> def clap():
... print("Clap! Clap!")
...
>>> # define a higher order function
>>> def triple_repeat_wrapper(func):
... def wrapper():
... print(f"Before calling func {func.__name__}")
... func()
... func()
... func()
... print(f"After calling func {func.__name__}")
... return wrapper
...
In the code above, we define two functions (clap
and triple_repeat_wrapper
), the latter of which is a higher-order function that calls the passed func
three times within the nested wrapper
function and returns the wrapper
function as the higher-order function’s output.
How can we use these functions? As shown in the code below, we create a function called wrapped_clap
that passes the clap
function to the higher-order function triple_repeat_wrapper
. As you can see, calling the wrapped_clap
function will result in the code inside the nested wrapper
function to be called. Consistent with the printed output, we get to know that the wrapped_clap
function references the nested function wrapper
.
>>> wrapped_clap = triple_repeat_wrapper(clap)
>>> wrapped_clap()
Before calling func clap
Clap! Clap!
Clap! Clap!
Clap! Clap!
After calling func clap
>>> wrapped_clap
<function triple_repeat_wrapper.<locals>.wrapper at 0x1038f0680>
But if you recall, when you’ve seen some example code about decorators, you must have seen the use of the @ symbol. How does this symbol come into play with the functions that we defined above? Let’s address this question by referring to the following code:
>>> @triple_repeat_wrapper
... def hooray():
... print("Hooray! Hooray!")
...
>>> hooray()
Before calling func hooray
Hooray! Hooray!
Hooray! Hooray!
Hooray! Hooray!
After calling func hooray
>>> hooray
<function triple_repeat_wrapper.<locals>.wrapper at 0x1038f0830>
In the code above, we declare a function called hooray
. When we call this function, the output has the same format as the previous wrapped_clap
function’s output. Similarly, the hooray
function references the nested wrapper
function.
Why does this happen? As you may have noticed, right above the declaration of this hooray
function, we use the @ symbol prefixing the triple_repeat_wrapper
function name. It’s just syntax sugar for the decorators. In essence, we’re telling the Python interpreter that the function that we’re going to define will be wrapped by the decorator function.
Named after the mathematician Haskell Curry, currying refers to creating new functions from existing functions by applying partial arguments. Thus, this concept is sometimes also termed partial functions.
Compared to the concepts above, this one is slightly easier to understand. Let’s consider the simplified example below, using the same function add_up
that we defined above:
>>> # define a function returns a value
>>> def add_up(num1, num2):
... sum_n = num1+num2
... return sum_n
...
>>> # define a partial function that adds seven
>>> add_seven = lambda x: add_up(7, x)
>>> add_seven(10)
17
>>> add_seven(72)
79
>>> # use a regular def keyword
>>> def add_eight(x):
... return add_up(8, x)
...
>>> add_eight(10)
18
In the code above, we use the lambda function to set the number 7
as the first argument for the add_up
function. In other words, the created add_seven
function is a partial function of the original add_up
with the first argument constantly set to be 7
. Besides using the lambda function, it’s certainly acceptable to use the regular way to define a function with the def
keyword.
Another handy tool to create partial functions is available in the functools
module. Consider the following example. We use the partial
function to create the add_ten
function that has a default argument of 10
that’s calling the add_up
function:
>>> # import the module
>>> from functools import partial
>>> # using the partial function
>>> add_ten = partial(add_up, 10)
>>> add_ten(100)
110
In this article, we reviewed four advanced concepts beyond the basics of Python functions. Here is a quick recap of these concepts:
Thank you for reading!
#python #artificial intelligence #technology