Python Functions and Best Practices

12 min read

Python functions are small, reusable blocks of code that you can utilize throughout a Python project. The standard Python library provides many useful built-in functions such as print(), len(), str(), int() but you can also define your own functions that can be used in your code. In this article, you'll learn how to write more concise, robust and readable Python code using functions.

When to use functions?

As your Python project starts to get more complex you'll usually find yourself having to write the same code repeatedly. Writing repetitive code is a bad idea because aside from the obvious fact that it’s tedious, it adds unnecessary visual clutter to your code base, it’s prone to human error if it’s more than a few lines, it will need to be changed in multiple places if you need to add new functionality to that process (say better error handling) and finally you have no way of writing unit or integration tests.

So instead of writing repetitive code, you can use Python functions. When is the right time to use functions? Well, it's really up to you but as a rule of thumb if you find yourself writing the same code pattern more than twice then you should probably think about investing some time into writing a function that you can then reuse whenever you need to in your codebase. There’s no shame in going back to change your old code to use a function if you start to see that you’re needing to write the same thing over and over. That’s what refactoring is!

Functions are extremely useful because they allow a piece of code to be written once and then invoked in a single line wherever needed throughout your code. You then only need to make improvements to one place in your code and if the function is well named then whoever reads through your code can easily infer what the function does by looking at its name and arguments instead of reading multiple lines of code.

Defining a Function

A simple example

Here is a simple example of how we can define a function (lines 1 -3) that adds two numbers together. We'll then assign the value that the function returns to a variable called sum (line 5) and print (line 6) the value of that out to the console.

def add(a, b):
   result = a + b
   return result


sum = add(5, 7) print(sum) # 12

Okay, let's walk through the definition of our function from lines 1 to 3. Starting a line with def tells the Python interpreter that we are defining a function, we then name the function and specify the parameters, the data that the function takes as input in parenthesis. In this case, we specify parameters as a and b. On the second line, we tell Python that whatever is supplied as a and b should be added together and the result should be assigned to a variable called result. Finally, on line 3, we tell the function to return the result variable. 

Note that it is a convention to have two blank lines after you define a function.

It's also a common convention to return a Python expression directly, instead of assigning it to a variable. Here's another way that we can re-write the same function to be more concise:

def add(a, b):

return a + b

A more complex example

At this point you might be thinking, why is this useful when I could just write a + b in my code? Well, let's take a look at a slightly more complex example where we create a function that returns the highest number in a list of numbers.

def highest_number(numbers): highest = 0 for number in numbers:

if number > highest: highest = number return highest

So, in this case, we've specified a parameter called numbers which we then loop through on line 3. Finally, our function should return a value, in this case, the highest number. If you don't specify anything to be returned, your function will return None

Note that if your function name is more than one word you should use 'snake_case'.

Now that we’ve defined our highest_number() function in our Python code we can call it, pass it an argument (a list of scores in this case) and assign the result it returns to a variable:

scores = [21, 14, 72, 148, 54]

top_score = highest_number(scores)


print(top_score)

# 148

See how clean that is? We now have a function that performs several operations but is concisely expressed in a single line and the result can be assigned to a variable called top_score. We can now use this function wherever we need to in our code and if we make improvements to that function, say to handle a scenario where someone supplies a string as an argument instead of a list, then every place in our code that uses the function will receive that new enhancement automatically. 

Additionally, we can now also write unit tests for this function to test that it performs correctly with different scenarios like correct or incorrect input. Writing tests for your code may seem like an unnecessary burden when you're starting out but it becomes increasingly useful when you start building more complex Python programs. For example, if you're building a web application with Django you'll have a bunch of URLs that all point to functions which render various web pages. As your website grows you don't want to manually check that every page is still working when you make a change to your code, it's much faster and more effective to run a suite of tests and get notified if anything is broken.

Now that we're more familiar with how functions work, let's examine the various aspects of a Python function more closely.

Parameters & arguments

Parameters are specified in parenthesis when you're defining a function, arguments are the data that you pass into the function's parameters when you call the function. In the case with the code below, a and b are the parameters for the add() function, whereas 5 and 7 are the arguments that are passed into the function when it is called.

Functions can have multiple parameters separated by a comma, like this:

def add(a, b):
    return a + b


result = add(5, 7)

When you call a function you need to supply arguments for all of the parameters that the function expects, otherwise, the Python interpreter will raise a TypeError and your code will stop running if this error is not handled. You'll also need to supply your arguments in the same order that the function parameters are specified so that Python assigns the arguments to the correct parameters.

As a rule of thumb, you should try to keep the number of parameters for a function as low as possible so that it's easier for you and other people to remember what arguments your function takes and in what order. Having fewer parameters also reduces the temptation to try and do too much work inside your function. Functions should have a single responsibility, which we'll discuss as part of best practices further on.

Keyword arguments

Keyword arguments are liked named parameters, this is how you can define and use them: 

def contains(word, sentance, match_case=False): word_list = sentance.split(" ") if not match_case: for item in word_list: if item.lower() == word.lower(): return True else: for item in word_list: if item == word: return True return False

word = "Amazon"

sentance = "The Amazon is the world’s largest tropical rainforest" result = contains(word, sentance, match_case=True) print(result) # True

They can make your code easier to read and also allow you to specify a default argument if no argument has been provided when the function is called. In the above example, the contains() function will execute with match_case=False by default, if it is not specified when called. While normal arguments need to be in the correct order and passed into a function before keyword arguments, keyword arguments don't need to be called in any specific order as they are named and so the Python interpreter knows where to assign them. Just remember that keyword arguments must always be put after normal parameters when defining and calling your function.

return

The return statement is used to tell your function to return a value. Typically you will assign the result returned from the function to a variable. In the code below, we'll define a function and then call it and have the result assigned to the result variable.

def is_even(num):

if num % 2 == 0:

return True

return False


result = is_even(6)


print(result)

# True

Notice that we can have more than one return statement for different results. Once the Python interpreter reaches a return statement, it will return the expression that follows it and no other code will be run. The % (modulus) operator used above returns only the remainder of a division calculation. So you write 5 % 2 Python will divide 5 by 2 which equals 2.5 and only return the number to the right of the decimal point, in this case, 0.5. Using the modulus operator to divide by two is a common technique used to check if a number is even.

*args

At some point, you may come across a function definition that has a strange looking parameter with an * prefix, like this:

def throw_party(host, *args): print(host, "is throwing a party!") for guest in guests: print(guest, "has arrived.")

Basically, the * operator is used to handle an unknown number of arguments. You'd use this when you may have a situation where you could have a variable number of arguments that you'd want to handle. For example, now we can use this function with any number of guests:

throw_party("Anika", "Jethrow", "Roxanne", "Sadiyah", "Andrei")

which will produce the following result:

Anika is throwing a party!

Jethrow has arrived.

Roxanne has arrived.

Sadiyah has arrived.

Andrei has arrived.

You don't have to name this parameter as *args, it's just a typical convention that is used. You could just as well use *guests and your code would continue to work just the same.

**kwargs

Similarly, you might see a function that makes use of a **kwargs parameter like this:

def scoreboard(game, **kwargs):
    print(game, "scores:") for key, value in kwargs.items(): print(key, value)

**kwargs is similar to *args except it is used to tell a function to accept any number of keyword arguments, which can be called like this:

scoreboard("Space Invaders", player_a=98, player_b=250, player_c=176)

this will then output the following results:

Space Invaders scores:

player_a 98

player_b 250

player_c 176

Now that we've looked at the different ways that you can define functions in Python let's look at some best practices.

Best Practices

Single responsibility principle

You may have heard of the single responsibility principle. This means that your functions should only focus on handling a single aspect of your program. 

For example, say that you want to write some code that does three things: fetch a web page, extract data from the page and print the data out to the terminal. You wouldn't write a function that extracts the data from the web page AND prints the results out to the terminal, because perhaps one day you might need to change your code to save those results to a file instead or push them to an API. 

If you try to handle all three of these output cases in your function, you'd probably end up having to add another parameter to specify which output to use, and then add that argument to every place in your code that calls the function. Then what about specifying a filename for when saving the results to a file? Or the URL and access credentials for pushing the results to an API? You can see how breaking your code down into smaller reusable functions prevents it from becoming exponentially more complicated and keeps it flexible for reusing around your codebase.

Naming your functions

A well-named function should almost read like a sentence when used. When someone is reading the code it should be immediately obvious what is happening in the code. A good rule of thumb is to design and name your function so that it can be used to simplify a more complex set of operations into a single expression. For example, we can reuse the function we wrote earlier in this article to perform all of those operations in an expression on line 3 below:

sentance = "The tiger is the biggest species of the cat family"


result = contains("tiger", sentance)


print(result)

# True

Docstrings

When you write your Python functions you can write docstrings to add inline documentation to your functions. They can be one-liners or multi-line docstrings. Here is an example of how you'd write a one-liner docstring for a function:

def double(*numbers):

"""Take numbers as args, return a list of the numbers doubled."""

results = []

for number in numbers:

results.append(number*2)

return results


doubled_list = double()

There are a few things to note about docstrings:

  • they should be enclosed in three double quotes with no space at the beginning or end
  • they should always occur on the first line of a function, with no blank line above or below it
  • they should be written as a concise sentence in the tone of a command (take this and return that) instead of a description (takes argument and returns a value) and end with a period. 
  • try to indicate what data type or data structure the function will return (e.g. number, string, list, dictionary)

By doing this, when people start using your functions their IDE will usually display the docstring to them to assist when using your function. This is a useful way to provide more information about the parameters your function accepts and what it returns.

Multi-line docstrings should have one summary line followed by a blank line and then a description.

Organizing your functions

Once you start writing multiple functions for your Python project you should think about how you want to organise them. If it's a simple project with only a few functions you may want to define them at the beginning of your Python module. If your project size is a bit bigger you may want to put them into their own module so that they can be imported into your code and used wherever. You may also want to create methods for classes. These topics are beyond the scope of this article for now but are worth you doing some research on to learn more.

That's it for your introduction to Python's functions! Once you've had some practice it's recommended that you start learning about Python Classes. Classes are the foundation of object-oriented programming in any language and are an essential part of professional programming.


Author picture
Rhett Trickett

Founder at Able

@rhett RhettTrickett rhetttrickett
Python

Join the discussion

Able is a developer community where people build their coding knowledge and careers.

Join with GitHub Join with Twitter