Functions (Need)

From xkcd.

What to Expect in thie Chapter

By now, you will be comfortable using functions. In addition, I hope you are satisfied with the idea that a function is just a chunk of code that does a specific conceptual task. In this chapter, I will show you how to craft your own functions. In addition to its practicality, the modularity of functions prompts us to think of solutions about modular solutions to problems.

User defined functions

print() is an example of an internal function in Python. You can also create your own functions. There are two ways to do this: named and anonymous.

Named functions that return

We define the function by:

def greeting(name):
    if name == 'Batman':
        return 'Hello Batman! So, nice to meet you!'
    else:
        return f'Hello {name}!'

and use it as:

greeting("Super Man")

or

greeting(name="Super Man")

The function’s name is greeting and it accepts a single argument called name.

Notice the keyword def, the colon (:) and the indentation that demarcates the function’s code block. Notice also that I have used the keyword return to get an output from the function. When Python sees a return keyword it jumps out of the function with the return value. You can pick up the returned value by assigning it to a variable or even use it directly like:

greet=greeting(name='Super Man')
print(greet)

or this works too:

print(greeting(name='Super Man'))

Incidentally, you can use return only within a function.

I also like to point out that you can return most anything! Here is an example of a function that accepts a list and returns the maximum, minimum and mean.

def basic_stats(numbers):
    np_numbers=np.array(numbers)
    return np_numbers.min(), np_numbers.max(), np_numbers.mean()

Here is how you can use it:

list_min, list_max, list_mean = basic_stats([1, 2, 3, 4, 5])

Named functions that don’t return

A function does not have to return anything. A good example is print(), which does something but does not return a value. You will often need functions like these, for instance, to save data to a file. I will show you a few of such functions in the later chapters.

Anonymous functions

Anonymous or lambda functions are suitable for short one-liners. The following examples accept a single argument called name.

my_short_function = lambda name: f"Hello {name}!"

We can use it like

my_short_function(name="Super Man")

A lambda function always returns the value of the last statement.

The above example is not a very good ‘anonymous’ one because I have used a name! So let me show you another one where things are really anonymous.

Let’s say I want to sort a 2D list. I can use sorted() function for this as follows:

numbers=[[9, 0, 10],
         [8, 1, 11],
         [7, 2, 12],
         [6, 3, 13],
         [5, 4, 14],
         [4, 5, 15],
         [3, 6, 16],
         [2, 7, 17],
         [1, 8, 18],
         [0, 9, 19]]

sorted(numbers)                         # Sort by comparing the default key 
                                        # (i.e. the 1st element) 
[[0, 9, 19], [1, 8, 18], [2, 7, 17], [3, 6, 16], [4, 5, 15], [5, 4, 14], [6, 3, 13], [7, 2, 12], [8, 1, 11], [9, 0, 10]]

Notice that this sorting is based on comparing the first elements of the sub-lists. If I want to use some other criteria, then I need to specify a key that can be used for comparison. I can use a lambda function for this as follows.

sorted(numbers, key=lambda x: x[1])     # Sort by comparing a custom key
                                        # that uses the 2nd element
[[9, 0, 10], [8, 1, 11], [7, 2, 12], [6, 3, 13], [5, 4, 14], [4, 5, 15], [3, 6, 16], [2, 7, 17], [1, 8, 18], [0, 9, 19]]

This is really powerful! I can specify almost any criterion I like. For example, I can ask them to be sorted according to the sum of the elements of the sub-lists.

sorted(numbers, key=lambda x: sum(x))   # Sort by comparing a custom key
                                        # that uses the sum of the elements
[[9, 0, 10], [8, 1, 11], [7, 2, 12], [6, 3, 13], [5, 4, 14], [4, 5, 15], [3, 6, 16], [2, 7, 17], [1, 8, 18], [0, 9, 19]]

Optional arguments

Python allows us to make arguments to our function optional. To do this, we need to give the argument a default value so that it always has something to work with.

def greeting(name='no one'):
    if name == 'Batman':
        return 'Hello Batman! So, nice to meet you!'
    else:
        return f'Hello {name}!'

Now we can run this function without an argument, and it will still work without throwing an error.

greeting()
'Hello no one!'

For another example, let’s look at the documentation for print().

?print
    Docstring:
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.
    Type:      builtin_function_or_method

You see that print() can accept other arguments and are optional with default values. However, we can specify them if we like; here goes.

print('I', 'am', 'Batman!')                 # Just for comparison
## I am Batman!
print('I', 'am', 'Batman!', sep='---')  
## I---am---Batman!

The importance of functions?

An argument for functions

Now that you know a bit about creating functions, let me highlight why functions are a good idea.

Abstraction of details The most important benefit of functions goes beyond programming and relates to your ability to strategise. If you break up a complicated solution into modular chunks (i.e. functions), it becomes easier to think about it because you are not dealing with all the details all at once. As a result, it is easier to focus on your overall solution because you are not distracted by unnecessary information. This hiding of ‘stuff’ is called abstraction.

The concept of abstraction can be tricky to grasp. So, let me share an analogy related to driving. A vehicle has many abstracted systems, amongst which the engine is a good example. You do not need to know the engine’s details (e.g. electric, petrol, diesel, guineapig) to use it. You can use the engine of almost any car because you are not required to know what happens inside. This frees up your resource because you are not distracted by unnecessary details. Of course, there will be times when you want to know how an engine works to pick the best engine, but this is not all the time.

Reusability of code If you encapsulate a chunk of code in a function, it becomes straightforward to reuse instead of copying and pasting at different places. This means your code will be shorter and more compact.

Maintainability of code With functions, your code is easier to change and maintain because you need only make changes in one place, at the function definition.

A word of caution

I have seen instances where functions are abused; for example, by trying to do too much or having too many arguments. They can also be overused. Having too many functions can make it difficult to read your code and also increase computational overheads. You will get a better feel for when to use functions with experience, but please bear in mind that functions can be misused.