This guest post by Fabrizio Romano, the author of Learn Python Programming – Second Edition, explores why functions are integral to developing applications in Python.
Functions are among the most important concepts and constructs of any language; here are a few reasons:
- They reduce code duplication in a program. By having a specific task taken care of by a nice block of packaged code that you can import and call whenever you want, you don’t need to duplicate its implementation.
- They help in splitting a complex task or procedure into smaller blocks, each of which becomes a function.
- They hide the implementation details from their users.
- They improve traceability.
- They improve readability.
Let’s look at a few examples to get a better understanding of each point.
Reducing code duplication
Imagine that you are writing a piece of scientific software, and you need to calculate primes up to a limit. You have an algorithm to calculate them, so you copy-paste it to wherever you need. One day, though, your friend, B. Riemann, gives you a better algorithm to calculate primes, which will save you a lot of time. At this point, you need to go through your entire code base and replace the old code with the new one.
This is, in fact, a bad way of going about it. It’s error-prone as the process entails the risks of faulty code replacement and deletion of crucial code parts, making your algorithm inconsistent and unstable. What if, instead of replacing code with a better version of it, you need to fix a bug, and you miss one of the places? That would be even worse.
So, what should you do? Simple! You write a function, get_prime_numbers(upto), and use it anywhere you need a list of primes. When Riemann comes to you and gives you the new code, all you have to do is replace the body of that function with the new implementation, and you’re done! The rest of the software will automatically adapt, since it’s just calling the function.
Your code will be shorter as it will not suffer from inconsistencies or undetected bugs due to copy-and-paste failures or oversights.
Splitting a complex task
Functions are also very useful for splitting long or complex tasks into smaller ones. The end result is that the code benefits from it in several ways, for example, readability, testability, and reuse.
To give you a simple example, imagine that you’re preparing a report. Your code needs fetch, parse, filter, and polish data, and then perform an entire series of algorithms against the data to generate a report. It isn’t uncommon to read procedures that are just one big do_report(data_source) function. There are tens or hundreds of lines of code that end with return report.
These situations are slightly more common in scientific code; they tend to be brilliant from an algorithmic point of view but lack the touch of experienced programmers when it comes to the style. Now, picture a few hundred lines of code. It’s very hard to follow through to find the places where things are changing context (such as finishing one task and starting the next one). Do you have the picture in your mind? Good. Don’t do it! Instead, look at this code:
def do_report(data_source): # fetch and prepare data data = fetch_data(data_source) parsed_data = parse_data(data) filtered_data = filter_data(parsed_data) polished_data = polish_data(filtered_data) # run algorithms on data final_data = analyse(polished_data) # create and return report report = Report(final_data) return report
The previous example is fictitious, of course, but can you see how easy it would be to go through the code? If the end result looks wrong, debugging each of the single data outputs in the do_report function will be extremely easy. Moreover, it’s even easier to exclude parts of the process temporarily from the whole procedure (you just need to comment out the parts you need to suspend).
Hiding implementation details
Refer to the preceding example to appreciate this merit as well. As you can see, by going through the code of the do_report function, you can get a pretty good understanding without reading one single line of implementation.
This is because functions hide the implementation details, meaning you don’t need to delve into the details if you don’t want to, as against the case where do_report were just one big, fat function. This reduces the time you spend reading the code. Since, reading code takes longer than actually writing it in a professional environment, it’s very important to reduce it by as much as we can.
Coders sometimes don’t see the point in writing a function with a body of one or two lines of code, so let’s look at an example that shows you why you should do it.
Imagine that you need to multiply two matrices:
Take a look at the following two code snippets and decide for yourselves which one is easier to read:
# matrix.multiplication.nofunc.py a = [[1, 2], [3, 4]] b = [[5, 1], [2, 1]] c = [[sum(i * j for i, j in zip(r, c)) for c in zip(*b)] for r in a] # matrix.multiplication.func.py # this function could also be defined in another module def matrix_mul(a, b): return [[sum(i * j for i, j in zip(r, c)) for c in zip(*b)] for r in a] a = [[1, 2], [3, 4]] b = [[5, 1], [2, 1]] c = matrix_mul(a, b)
In the second example, it’s much easier to understand that c is the result of the multiplication of a and b. Reading through the code is also easy and, if you don’t need to modify the multiplication logic, you don’t even need to go into the implementation details. On the other hand, in the first snippet, you would have to spend a lot of time trying to understand what that complicated list comprehension is doing.
Imagine that you have written an e-commerce website. You have displayed the product prices all over the pages. Imagine that the prices in your database are stored with no VAT, but you want to display them on the website with VAT at 20%. Here are a few ways of calculating the VAT-inclusive price from the VAT-exclusive price:
# vat.py price = 100 # GBP, no VAT final_price1 = price * 1.2 final_price2 = price + price / 5.0 final_price3 = price * (100 + 20) / 100.0 final_price4 = price + price * 0.2
All these four ways of calculating a VAT-inclusive price are perfectly acceptable. Now, imagine that you have started selling your products in different countries and some of them have different VAT rates. This means you’ll have to refactor your code throughout the website in order to make the VAT calculation dynamic.
How would you trace all the places in which you are performing a VAT calculation? Coding is generally a collaborative task and you cannot be sure that the VAT has been calculated using only one of those forms, can you?
Here’s what you do. Write a function that takes the input values, vat and price (VAT-exclusive), and returns a VAT-inclusive price:
# vat.function.py def calculate_price_with_vat(price, vat): return price * (100 + vat) / 100
Now you can import this function and use it throughout your website to calculate a VAT-inclusive price, and when you need to trace those calls, you can search for calculate_price_with_vat.
Now that you’ve understood why functions are so important, explore Learn Python Programming – Second Edition to understand the nuances of Python programming to develop efficient, stable and high-quality applications. The book is replete with real-world examples that will make the fundamentals of Python programming a piece of cake and is hence a must-have for all beginners.