DEV Community

Cover image for Write Code Once, Use It Forever: Python Functions Explained
Sylvia Ndili
Sylvia Ndili

Posted on

Write Code Once, Use It Forever: Python Functions Explained

After working with variables, conditions and loops, we can now cover functions.

The easiest way I got to understand functions was to think about repeated work. For instance, if I have to calculate VAT for three different prices, I could write the calculation three times. But if I need the same calculation in ten or twenty places, repeating the same code quickly becomes a hassle.

Essentially what I'm getting at is that, a function lets you write the logic once and reuse it.

The problem with repeating code

Without a function, calculating VAT for three items could look like this:

price1 = 1000
vat1 = price1 * 0.16
total1 = price1 + vat1

price2 = 2500
vat2 = price2 * 0.16
total2 = price2 + vat2

price3 = 750
vat3 = price3 * 0.16
total3 = price3 + vat3
Enter fullscreen mode Exit fullscreen mode

The calculation itself isn't complicated. The problem is repetition. If the VAT rate changes from 16% to 14%, we would have to find every place where 0.16 was used and change it.

A function gives you one place to maintain that logic.

def add_vat(price, rate=0.16):
    vat = price * rate
    total = price + vat
    return total
Enter fullscreen mode Exit fullscreen mode

Now it can be reused:

print(f"Item 1: Ksh {add_vat(1000)}")
print(f"Item 2: Ksh {add_vat(2500)}")
print(f"Item 3: Ksh {add_vat(750)}")
Enter fullscreen mode Exit fullscreen mode

If the rate needs to change, we can change the default in the function instead of changing every calculation.

Defining and calling a function

A function starts with def.

For example:

def greet_student(name):
    print(f"Good evening, {name}! Welcome to Python Class.")
Enter fullscreen mode Exit fullscreen mode

At this point, the fucntion has been defined. But it doesn't run just because we defined it. A function needs to be called as shown below:

greet_student("Moses")
greet_student("Sylvia")
greet_student("Amina")
Enter fullscreen mode Exit fullscreen mode

The function runs each time you call it, using the name provided. This is one of the main benefits of functions: define the behaviour once, then reuse it with different values.

Functions can have more than one parameter

You can also pass several pieces of information:

def student_info(name, age, track):
    print(f"Name:  {name}")
    print(f"Age:   {age}")
    print(f"Track: {track}")
    print()

student_info("Sylvia", 26, "Data Science")
student_info("Moses", 20, "Data Engineering")
Enter fullscreen mode Exit fullscreen mode

Here, name, age and track are parameters. When you call the function, you provide the actual values.

You can also use keyword arguments to make it clear which value belongs to which parameter:

describe_student(
    track="Data Science",
    name="Jane",
    city="Nyeri"
)
Enter fullscreen mode Exit fullscreen mode

Printing isn't the same as returning

This is a really important distinctions.

Consider:

def add_print(a, b):
    print(a + b)

result = add_print(6, 5)
print(result)
Enter fullscreen mode Exit fullscreen mode

The function prints 11, but result becomes None. That's because the function displayed the answer but didn't give the answer back to the program. If we want to use the result later, we need to use return.

def add_return(a, b):
    return a + b
Enter fullscreen mode Exit fullscreen mode

Now you can do something with the result:

result = add_return(5, 10) * 2
print(result)
Enter fullscreen mode Exit fullscreen mode

This gives: 30

Functions can contain decisions

A function can also contain conditional logics.

For example, create a function to determine a grade:

def get_grade(score):
    if score >= 80:
        return "A"
    elif score >= 70:
        return "B"
    elif score >= 60:
        return "C"
    elif score >= 50:
        return "D"
    else:
        return "F"
Enter fullscreen mode Exit fullscreen mode

The grading logic doesn't need to be repeated every time there is a score. You can use it with a list by adding the below:

scores = [87, 74, 55, 91, 43]

for score in scores:
    grade = get_grade(score)
    print(f"Score: {score} Grade: {grade}")
Enter fullscreen mode Exit fullscreen mode

This is where functions and loops work together. The loop goes through the scores, while the function handles the grading logic.

Returning multiple values

Functions can return more than one thing. In this case, create a function that analyses a list of scores:

def analyse_score(scores):
    total = sum(scores)
    average = round(total / len(scores), 1)
    highest = max(scores)
    lowest = min(scores)

    return total, average, highest, lowest
Enter fullscreen mode Exit fullscreen mode

To receive all four results:

scores = [87, 74, 55, 91, 43, 78, 88]

tot, avg, hi, lo = analyse_score(scores)

print(f"Total:     {tot}")
print(f"Average:   {avg}")
print(f"Highest:   {hi}")
print(f"Lowest:    {lo}")
Enter fullscreen mode Exit fullscreen mode

The variables: tot, avg, hi, lo, receive the four values returned by the function. This is useful when one piece of logic needs to calculate several results that are related.

Scope: where variables exist

Another important concept is scope. A variable created inside a function is local to that function.

def calculate_fee():
    fee = 4000
    print(f"This is fee inside function: {fee}")

calculate_fee()
Enter fullscreen mode Exit fullscreen mode

This works because fee exists inside the function.

But: print(fee) outside the function would cause an error because that local variable doesn't exist there.

Another one is global variables:

school = "Nairobi Academy"

def show_school():
    print(f"School: {school}")

show_school()
print(f"Also here: {school}")
Enter fullscreen mode Exit fullscreen mode

A function can read the global variable.

So, what happens when a local variable has the same name as a global one:

name = "Nairobi"

def show_local():
    name = "Mombasa"
    print(f"Inside function: {name}")

show_local()
print(f"Outside function: {name}")
Enter fullscreen mode Exit fullscreen mode

The output is:

Inside function: Mombasa
Outside function: Nairobi
Enter fullscreen mode Exit fullscreen mode

The two name variables are separate.

The global keyword

You can also use the global keyword when a function needed to change a global variable.

For example:

login_count = 0

def login(username):
    global login_count
    login_count += 1
    print(f"{username} logged in. Total logins is {login_count}")

login("Prudence")
login("John")
login("Daniel")
login("Martha")
Enter fullscreen mode Exit fullscreen mode

The counter is shared between the function calls. In practice, I also learned why keeping data local or passing it into functions can often make code easier to manage. global is something to use when it is actually needed, not simply because it is available.

Default arguments

Functions can also have default values.

For example:

def send_message(recipient, message, channel="SMS"):
    print(f"Sending to {recipient} via {channel}: {message}.")
Enter fullscreen mode Exit fullscreen mode

If you don't provide a channel: send_message("Brian", "Meeting at 3pm"), Python uses: SMS. But that can be overridden like this:

send_message(
    "Moses",
    "Your package has arrived",
    "WhatsApp"
)
Enter fullscreen mode Exit fullscreen mode

Default arguments are useful when there is a value that will normally stay the same but sometimes needs to be changed.

*args

The parameter *args, allows a function to receive a variable number of arguments.

For example:

def class_summary(teacher, *scores):
    print(f"Teacher: {teacher}")
    print(f"Students: {len(scores)}")

    if scores:
        print(f"Average: {sum(scores) / len(scores)}")
        print(f"Highest: {max(scores)}")

    print()
Enter fullscreen mode Exit fullscreen mode

Then you can call it with different numbers of scores:

class_summary("Mr. David", 78, 85, 91, 65, 72)
class_summary("Ms. Wanjiku", 88, 94, 76)
Enter fullscreen mode Exit fullscreen mode

The teacher is a required parameter, while *scores collects however many scores we provide.

Putting Functions Together: Payslip Generator Program

To understand functions better, I did a simple program which is a Payslip generator. Instead of putting all the salary calculations into one large block of code, we separated the different deductions into their own functions.

def calculate_nhif(gross):
    if gross < 6000:
        return 150
    elif gross < 12000:
        return 400
    elif gross < 25000:
        return 750
    else:
        return 950

def calculate_nssf(gross):
    return min(round(gross * 0.06), 2160)

def calculate_paye(gross):
    if gross <= 24000:
        return 0

    return round((gross - 24000) * 0.16)

def print_payslip(name, gross):
    nhif = calculate_nhif(gross)
    nssf = calculate_nssf(gross)
    paye = calculate_paye(gross)

    deductions = nhif + nssf + paye
    net = gross - deductions

    width = 40

    print("=" * width)
    print(f"  PAYSLIP - {name}")
    print("=" * width)
    print(f"  Gross salary:  Ksh {gross:>9,}")
    print(f"  NHIF:          Ksh {nhif:>9,}")
    print(f"  NSSF:          Ksh {nssf:>9,}")
    print(f"  PAYE:          Ksh {paye:>9,}")
    print(f"  {'' * 36}")
    print(f"  NET PAY:       Ksh {net:>9,}")
    print("=" * width)
    print()

#generate payslips for different employees simply by calling the function

print_payslip("Amina Wanjiku", 85000)
print_payslip("Brian Otieno", 28000)
print_payslip("Njeri Kamau", 22000)
Enter fullscreen mode Exit fullscreen mode

Output:

Output

This can probably be seen as a simple and clear example of the importance of functions. Each function has one responsibility. The overall flow becomes easier to follow:

Gross salary
     ↓
Calculate NHIF
     ↓
Calculate NSSF
     ↓
Calculate PAYE
     ↓
Add deductions
     ↓
Subtract from gross
     ↓
Print payslip
Enter fullscreen mode Exit fullscreen mode

If I needed to change the logic for one deduction, I could work on that particular function instead of searching through one huge block of code.

Finally

The biggest thing I took from functions is that they help turn a program into smaller, reusable pieces. Instead of writing the same calculation repeatedly, we can put it inside a function and call it whenever we need it. You can also see that functions can work together with things we have already learned like: if/elif/else, loops, calculations, lists and f-strings.

The payslip generator brought that picure together really well. Each function handled a specific part of the problem, and the final function combined those pieces into something useful.

Top comments (0)