Lesson 13 - Declaring functions in Python

Python Basics Declaring functions in Python

Today's Python lesson will cover a very important topic - functions. Writing all the code straight in the global scope was all well and good for our small educational programs which usually could only do a single thing. However, consider writing a program which is thousands of lines long. I'm sure you agree it'd be very hard to work with such a "noodle" of code if it was all in a single file and a single logical block. Even more so, if we wanted to perform the same command sequences at multiple places in our program. In this case, we'd have to copy it all over again or to jump from one program place to another. Both options are very confusing.

Functional decomposition

We sometimes refer to splitting a program into multiple functions as a functional decomposition. Don't let the term intimidate you, we'll just think about what our application needs to do and then create a function in our source code for each of those tasks. In real-life applications, we usually create auxiliary functions as well. For example, a function for printing the application menu, or splitting a complex function to various simpler functions to keep the program readable.

Functions are sometimes called subroutines or subprograms. If a function doesn't return a value (more on this further along), it may be referred to as a procedure in some programming languages. Functions for larger applications, where there is a lot of them, are gathered into multiple modules/libraries. You know these very well already, e.g. from writing import math which loads the library (module) for working with mathematical functions. We'll also learn to create said modules later on.

Creating functions

A function is a logical block of code which we write once and then call it multiple times without needing to write it all over again. We'll declare functions in the global scope, somewhere above our code. Let's add a function to our source code which will write "Hi, welcome!".

We'll show the entire source code just to be illustrative:

#!/usr/bin/python3

def greet():
        print("Hi, welcome!")

We define functions using the def keyword. The empty parentheses indicate that the function doesn't have any input parameters. As you can see, the only thing the function does is printing text. Now, we have to call the function to execute it. Of course, we'd only be able to do so after we declare it, otherwise, the interpreter wouldn't recognize the function. Let's call the function under its declaration:

def greet():
        print("Hi, welcome!")

greet() # calling the function

The result:

Console application
Hi, welcome!

Functions with parameters

A function can have any number of input parameters (they're sometimes called arguments) which we write into the parentheses in its definition. We influence a function's behavior by parameters. Consider a situation we want to greet our users by their names. So let's extend our function of a name parameter and specify it later with a concrete value when calling the function:

def greet(name):
        print("Hi %s, welcome here!" % (name))

Now, we'll modify the calling of the function like this:

greet("Carl") # calling the function

If we wanted to greet multiple people, we wouldn't have to write print("Hi ... for each of them. Instead, we'll simply call our function:

greet("Carl")
greet("David")
greet("Mary")

The result:

Console application
Hi Carl, welcome!
Hi David, welcome!
Hi Mary, welcome!

The function's return value

A function can also return a value. Let's leave our greeting example and create a function for computing the area of a rectangle. Furthermore, we'll make it so we're able to use the result in other calculations. Therefore, we won't write the result but return it as the return value. Every function can return 1 value using the return command which will also terminate the function, so any other code after it won't be executed. We specify the data type of the return value before the function definition. Add the following function to your program:

def rectangle_area(width, height):
        result = width * height
        return result

In real-world applications, our function would probably compute something more complex, so it'd actually be worthwhile to implement. However, as an example, a simple rectangle will serve just fine. We name functions using lowercase letters, whole words and using under_scores instead of spaces. Avoid abbreviation names at all costs. For example, the birth_date() function is much clearer than bird() which makes it hard to tell what it even does at the first sight.

If we wanted to print the area of a rectangle now, we'd simply call our function straight in the print() function. First, the rectangle's area will be computed. Then, this value will be returned and passed as an input parameter to print() which will print it. Let's try it out by entering 10 and 20 cm as the width and height:

print("The area of the rectangle is: %d cm^2" % (rectangle_area(10, 20)))

Console application
The area of the rectangle is: 200 cm^2

If you find it confusing, feel free to use an auxiliary variable:

area = rectangle_area(10, 20)
print("The area of the rectangle is: %d cm^2" % (area))

However, we didn't decide to return the result as the function's return value to simply print it. Let's take advantage of this and compute the sum of the areas of two rectangles:

total_area = rectangle_area(10, 20) + rectangle_area(20, 40)
print("The sum of the areas of the rectangles is: %d cm^2" % (total_area))

The result:

Console application
The sum of the areas of the rectangles is: 1000 cm^2

Regarding previous exercises we did earlier on in the course: you may try to modify some of them and split them up into functions. According to good software design practices, a source code should always be split up into functions (and ideally into objects, more on this later on) to keep it clear. We omitted this at the beginning to keep things simple, but now, please, keep this in mind :)

The main advantage of using functions is clarity and keeping code shorter (we can write something once and call it a hundred times from multiple places in our program). If we decide to modify the function, we would only have to do it at one place and this change will affect all of the function calls immediately which decreases the possibility of making an error, significantly. In the greeting example, we could simply change the greeting text once in the function and it'd affect all three of the calls. If we didn't have the code in a function, we'd have to modify 3 sentences and it's very likely we'd make a typo somewhere.

Positional and keyword parameters

Python supports 2 kinds of function parameters. We've just explained and tried out positional parameters. We can also introduce parameters which are distinguished by their names, not the position in the parentheses. We call these parameters keyword parameters, we have to declare them after the positional ones and provide a default value for them so they won't need to be specified. Let's create a simple example:

def power(base, exponent = 2):
        result = base ** exponent
        return result

print(power(3))
print(power(3, exponent = 3))

Keyword parameters are useful in situations when they do something special and it'd be confusing to specify this behavior by simply passing a regular positional parameter. They're even needed in cases where the function has a variable number of positional parameters.

Variable number of parameters

A function can have a variable number of parameters, meaning we can pass as many parameters to it as we need in an exact case. We use the * operator to do so and we get all the parameters as a tuple.

def sum_numbers(*numbers):
        result = 0
        for number in numbers:
                result += number
        return result

print(sum_numbers(1, 2, 3))
print(sum_numbers(1, 2, 3, 40, 50, 60))

The output:

Console application
6
156

We can use the ** operator when we have a variable number of keyword arguments (they'll all come as a dictionary):

def keyword_test(**keywords):
        print(keywords)

keyword_test(one="1", two="2", three="3")
keyword_test()
keyword_test(name="Carl", password="Carlson")

The output:

Console application
{'three': '3', 'two': '2', 'one': '1'}
{}
{'password': 'Carlson', 'name': 'Carl'}

Remember the print() function and it's end parameter. We also use the sep keyword parameter to define the separator between the print() parameters. The function accepts any number of positional parameters and prints each of them.

print(1, 2, 3, sep="-", end="!")

The result:

Console application
1-2-3!

Recursion

To sum it all up, let's take a little peek into an advanced topic - recursion. A recursive function is a function which calls itself in its body. Such a function needs some information to determine when it should end. Otherwise, it'd call itself, then it would call itself again, and this would end the program terminating due to insufficient memory. Recursion is used very often in various algorithms.

In functional programming languages, recursion is used instead of loops. For example, a for loop that sums up the numbers from 1 to 10. We could achieve the same result with recursion as well. The function would call itself over and over with a number which increases by one 1 or it would terminate itself (depending on what the current number is).

def loop(current_index, final_index, sum):
        if current_index == final_index:
                return sum
        return loop(current_index + 1, final_index, sum + current_index)

We'd call the function like this:

print(loop(0, 10, 0)) # beginning of the recursion

We could do the same using a for loop:

sum = 0
for a in range (0, 10):
        sum += a
print(a)

As you can see, reading code using recursion is not as easy as reading a code using loops. However, that's not all. Using recursion creates additional memory requirements since parameters and return values have to be passed over and over again. Generally speaking, most programs which use recursion can be rewritten to not do so. Let's create a sample program that computes a factorial. We'll make versions with and without recursion.

def factorial(x):
        if x == 1:
                return 1
        return x * factorial(x - 1)

We'd call the function like this:

print(factorial(10))

Here's the alternative using loops:

result = 1
x = 10
for i in range (2, x + 1):
        result *= i
print(result)

You may run into recursion in existing source codes or at job interviews. However, it's probably better to avoid recursion, at least for now. Recursion is also able to waste the entire call stack quickly and terminate the program. Furthermore, it's difficult to understand. If you're confused by it, you're going to get more acquainted with it in the algorithms course where there's enough room to explain it in further detail.

Well, that's all I've got for you in this course. If you'd like to learn more about the Basic constructs of Python or feel like you need more practice, take another look at the articles and lesson-specific exercises. Our Python course will be continued in Basics of object-oriented programming in Python. In the next lesson, we'll introduce you to an object-oriented world. We'll get acquainted with many things that have been kept hidden from us until now :)


 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Thumbnail
All articles in this section
Python basic constructs
Activities (5)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!