User-Defined Functions

User-Defined Functions

In the previous lesson, we used built-in Python functions such as print(), type(), int(), float(), str(), len(), round(), and abs().

Now we will create our own functions.

A user-defined function is a reusable block of code that performs a specific task.

We use functions to:

  • avoid repeating code
  • give names to useful actions
  • organize programs into smaller parts
  • reuse the same logic with different inputs

Creating a Function

In Python, functions are created using the def keyword.

def <function_name>(<argument>):
    <code_of_the_function>

The def keyword marks the beginning of a function definition.

The function name comes after def.

The code inside the function must be indented.


Defining and Calling a Function

This code defines a function:

def greet(name):
    print(f"Hello, {name}!")

This cell does not print anything.

Python has learned that the function greet() exists, but we have not used it yet.

To use a function, we call it.

greet("Jorge")
Hello, Jorge!

We can call the same function many times.

greet("Ana")
greet("Luis")
greet("Marta")
Hello, Ana!
Hello, Luis!
Hello, Marta!

The value inside the parentheses is called an argument.

Exercise

Write a function called say_goodbye(name).

It should print a goodbye message using the name.

Example:

say_goodbye("Ana")

Expected output:

Goodbye, Ana!
def say_goodbye(name):
    print(f"Goodbye, {name}!")

say_goodbye("Ana")
Goodbye, Ana!

Functions Help Us Avoid Repetition

Imagine we want to print multiplication tables.

Without a function, we would repeat the same structure many times.

With a function, we write the logic once.

def multiplication_table(x):
    for n in range(1, 11):
        print(f"{x} x {n} = {x * n}")

Now we can reuse it with different numbers.

multiplication_table(3)
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15
3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
3 x 10 = 30
multiplication_table(8)
8 x 1 = 8
8 x 2 = 16
8 x 3 = 24
8 x 4 = 32
8 x 5 = 40
8 x 6 = 48
8 x 7 = 56
8 x 8 = 64
8 x 9 = 72
8 x 10 = 80
Exercise

Write a function called countdown(number).

It should print the numbers from number down to 1.

Example:

countdown(5)

Expected output:

5
4
3
2
1
def countdown(number):
    for n in range(number, 0, -1):
        print(n)

countdown(5)
5
4
3
2
1

Positional Arguments

A function can receive more than one argument.

def <function_name>(<argument_1>, <argument_2>):
    <code_of_the_function>

For example, this function takes a student’s name and grade, then says if the student passed.

def check_pass(name, grade):
    if grade >= 5:
        print(f"{name} has passed!")
    else:
        print(f"{name} has failed")
check_pass("Luke", 8)
Luke has passed!
check_pass("Yoda", 2)
Yoda has failed

The arguments above are positional arguments.

That means Python uses their position to decide what each value means.

check_pass("Leia", 6)
Leia has passed!

Here:

  • "Leia" goes into name
  • 6 goes into grade

The order matters.

check_pass(6, "Leia")
TypeError: '>=' not supported between instances of 'str' and 'int'

This fails because Python tries to compare "Leia" with 5.

Exercise

Without running the code, guess the output.

def describe_pet(name, animal):
    print(f"{name} is a {animal}")

describe_pet("Nina", "cat")
describe_pet("dog", "Rex")
Nina is a cat
dog is a Rex

The second call works, but the order is wrong. Python does not know that "dog" is probably the animal and "Rex" is probably the name.


Checking Input Types

Sometimes a function receives arguments with the wrong type.

We can check the type before continuing.

def check_pass(name, grade):
    if type(name) != str:
        print("Name must be a string")
        return

    if type(grade) != int and type(grade) != float:
        print("Grade must be a number")
        return

    if grade >= 5:
        print(f"{name} has passed!")
    else:
        print(f"{name} has failed")
check_pass("Luke", 8)
check_pass("Yoda", 2)
check_pass(6, "Leia")
Luke has passed!
Yoda has failed
Name must be a string

The return statements stop the function early.

Exercise

Improve the function below so that it checks whether x and y are numbers.

def addition(x, y):
    print(x + y)
def addition(x, y):
    if type(x) != int and type(x) != float:
        print("x must be a number")
        return

    if type(y) != int and type(y) != float:
        print("y must be a number")
        return

    print(x + y)

Exercise

Write a function called rectangle_area(width, height).

It should return the area of a rectangle.

Then use it to calculate the area of:

  • a rectangle with width 3 and height 4
  • a rectangle with width 10 and height 2
  • a rectangle with width 5.5 and height 3 :::
def rectangle_area(width, height):
    return width * height

print(rectangle_area(3, 4))
print(rectangle_area(10, 2))
print(rectangle_area(5.5, 3))
12
20
16.5

Example: Odd Index Characters

We can write a function that returns the characters in odd index positions.

def odd_index_chars(text):
    return text[1::2]

print(odd_index_chars("abcdefg"))
bdf

Remember that Python starts counting at 0.

For the string "abcdefg":

  • a has index 0
  • b has index 1
  • c has index 2
  • d has index 3

So the odd index characters are b, d, and f.

Exercise

Write a function called first_half(text).

It should return the first half of a string.

Example:

first_half("python")

Expected result:

pyt
def first_half(text):
    middle = len(text) // 2
    return text[:middle]

print(first_half("python"))
pyt

Example: Sum of Squares

Write a function that calculates the sum of the squares of all integers from 1 to n.

For example, if n = 4, the result is:

(1^2 + 2^2 + 3^2 + 4^2 = 30)

def sum_of_squares(number):
    result = 0

    for x in range(1, number + 1):
        result = result + x ** 2

    return result
y = sum_of_squares(2)

print(y)

y = sum_of_squares(4)

print(y)
5
30
Exercise

Write a function called sum_from_1_to_n(number).

It should return the sum of all integers from 1 to number.

Example:

sum_from_1_to_n(5)

Expected result:

15
def sum_from_1_to_n(number):
    result = 0

    for x in range(1, number + 1):
        result = result + x

    return result

print(sum_from_1_to_n(5))
15

Example: Quadratic Function

We can also use functions to represent mathematical functions.

Consider:

(f(x) = a x^2 + b x + c)

def quadratic(a, b, c, x):
    y = a * x ** 2 + b * x + c
    return y
print(quadratic(1, 1, -6, 2))
0

The function above calculates:

(1 ^2 + 1 - 6)

::: {.callout-note}

Exercise

Use the function quadratic(a, b, c, x) to calculate:

(f(x) = 2x^2 - 3x + 1)

for:

  • x = 0
  • x = 1
  • x = 2
  • x = 3 :::
def quadratic(a, b, c, x):
    y = a * x ** 2 + b * x + c
    return y

print(quadratic(2, -3, 1, 0))
print(quadratic(2, -3, 1, 1))
print(quadratic(2, -3, 1, 2))
print(quadratic(2, -3, 1, 3))
1
0
3
10

Keyword Arguments

Keyword arguments are passed using the parameter name.

function(argument_name=value)

With positional arguments, order matters.

With keyword arguments, the name tells Python where the value goes.

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")
greet("Alice")
greet("Bob", greeting="Hi")
greet("Don Pepito", greeting="Hola")
Hello, Alice!
Hi, Bob!
Hola, Don Pepito!

Here, name is required.

greeting has a default value. If we do not provide it, Python uses "Hello".


Default Values

A default value makes an argument optional.

def root(x, n=2):
    return x ** (1 / n)

By default, the function computes the square root.

print(root(4))
2.0

But we can choose another root.

print(root(16, n=4))
2.0

Keyword arguments must come after positional arguments.

This works:

root(16, n=4)

This does not work:

root(n=4, 16)
Exercise

Write a function called apply_discount(price, discount=10).

It should return the price after applying the discount percentage.

Example:

apply_discount(100)

Expected result:

90

Example:

apply_discount(100, discount=25)

Expected result:

75
def apply_discount(price, discount=10):
    return price - price * discount / 100

print(apply_discount(100))
print(apply_discount(100, discount=25))
90.0
75.0

Example: Calculator With Keyword Arguments

Build a calculator function that accepts three arguments:

  • num1
  • num2
  • operation

If no operation is given, it should use addition.

def calculator(num1, num2, operation="addition"):
    if operation == "addition":
        return num1 + num2
    elif operation == "subtraction":
        return num1 - num2
    elif operation == "multiplication":
        return num1 * num2
    elif operation == "division":
        return num1 / num2
result = calculator(4, 2)
print(result)

result = calculator(4, 2, operation="addition")
print(result)

result = calculator(4, 2, operation="subtraction")
print(result)

result = calculator(4, 2, operation="multiplication")
print(result)

result = calculator(4, 2, operation="division")
print(result)
6
6
2
8
2.0

This function works, but it has two possible problems:

  • it does not handle unknown operations
  • it does not handle division by zero

We can improve it.

def calculator(num1, num2, operation="addition"):
    if operation == "addition":
        return num1 + num2
    elif operation == "subtraction":
        return num1 - num2
    elif operation == "multiplication":
        return num1 * num2
    elif operation == "division":
        if num2 == 0:
            return "Cannot divide by zero"
        return num1 / num2
    else:
        return "Unknown operation"
print(calculator(4, 2))
print(calculator(4, 2, operation="division"))
print(calculator(4, 0, operation="division"))
print(calculator(4, 2, operation="power"))
6
2.0
Cannot divide by zero
Unknown operation
Exercise

Add one more operation to the calculator: "power".

It should return num1 ** num2.

def calculator(num1, num2, operation="addition"):
    if operation == "addition":
        return num1 + num2
    elif operation == "subtraction":
        return num1 - num2
    elif operation == "multiplication":
        return num1 * num2
    elif operation == "division":
        if num2 == 0:
            return "Cannot divide by zero"
        return num1 / num2
    elif operation == "power":
        return num1 ** num2
    else:
        return "Unknown operation"

print(calculator(2, 3, operation="power"))
8

Example: Quadratic Function With Defaults

Let us return to the quadratic function.

(f(x) = a x^2 + b x + c)

This time, a = 1, b = 1, and c = -6 are default values.

def quadratic(x, a=1, b=1, c=-6):
    y = a * x ** 2 + b * x + c
    return y

If we only provide x, Python uses the default values.

result = quadratic(2)

print(result)
0

We can also replace the defaults.

result = quadratic(2, a=2, b=0, c=0)

print(result)
8
Exercise

Use the function above to calculate:

(f(x) = x^2 + x - 6)

for x = -3, x = 0, and x = 2.

Then use the same function to calculate:

(g(x) = 3x^2 - 2x + 1)

for x = 2.

def quadratic(x, a=1, b=1, c=-6):
    y = a * x ** 2 + b * x + c
    return y

print(quadratic(-3))
print(quadratic(0))
print(quadratic(2))

print(quadratic(2, a=3, b=-2, c=1))
0
-6
0
9

Example: Radioactive Decay

In this exercise, we will create a function to model radioactive decay.

Radioactive decay describes how the amount of a radioactive element decreases over time.

The formula is:

(N(t) = N_0 (1/2)^{t / T_h})

Where:

  • \(T_h\) is the half-life of the radioactive element
  • \(N(t)\) is the number of radioactive atoms at time \(t\)
  • \(N_0\) is the initial number of radioactive atoms

Create a function called radioactive_decay(n0, half_life, t).

It should return the number of atoms remaining at time t.

We will use default values for half_life and t.

def radioactive_decay(n0, half_life=5730, t=500):
    return n0 * (1 / 2) ** (t / half_life)

Carbon-14 has a half-life of approximately 5730 years.

remaining_carbon_14 = radioactive_decay(1000, half_life=5730, t=2000)

print(f"After 2000 years, there are approximately {remaining_carbon_14:.0f} Carbon-14 atoms remaining.")
After 2000 years, there are approximately 785 Carbon-14 atoms remaining.

Uranium-238 has a much longer half-life, approximately 4.468 billion years.

remaining_uranium_238 = radioactive_decay(100, half_life=4.468 * 10 ** 9, t=10 ** 9)

print(f"After 1 billion years, there are approximately {remaining_uranium_238:.0f} Uranium-238 atoms remaining.")
After 1 billion years, there are approximately 86 Uranium-238 atoms remaining.

::: {.callout-note}

Exercise

Use radioactive_decay() to answer this question:

A sample starts with 5000 atoms. The half-life is 100 years.

How many atoms remain after:

  • 0 years
  • 50 years
  • 100 years
  • 200 years :::
def radioactive_decay(n0, half_life=5730, t=500):
    return n0 * (1 / 2) ** (t / half_life)

print(radioactive_decay(5000, half_life=100, t=0))
print(radioactive_decay(5000, half_life=100, t=50))
print(radioactive_decay(5000, half_life=100, t=100))
print(radioactive_decay(5000, half_life=100, t=200))
5000.0
3535.533905932738
2500.0
1250.0

Common Mistakes

Forgetting the Parentheses

This refers to the function:

print

This calls the function:

print()

The same happens with our own functions.

greet
greet("Ana")

Forgetting the Colon

Incorrect:

def greet(name)
    print(f"Hello, {name}")

Correct:

def greet(name):
    print(f"Hello, {name}")

Wrong Indentation

Incorrect:

def greet(name):
print(f"Hello, {name}")

Correct:

def greet(name):
    print(f"Hello, {name}")

Using a Variable Outside the Function

def double(x):
    result = x * 2

print(result)

This fails because result was created inside the function.

A better version:

def double(x):
    result = x * 2
    return result

answer = double(5)

print(answer)

Printing Instead of Returning

def double(x):
    print(x * 2)

answer = double(5)

print(answer + 10)

This fails because answer is None.

Correct version:

def double(x):
    return x * 2

answer = double(5)

print(answer + 10)

Summary of Argument Types

Positional Arguments

Positional arguments are matched by order.

def subtract(a, b):
    return a - b

subtract(10, 3)

Here, a = 10 and b = 3.

Order matters.

Keyword Arguments

Keyword arguments are matched by name.

subtract(a=10, b=3)

This is clearer when a function has many arguments.

Default Values

Some arguments can have default values.

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

If no greeting is given, Python uses "Hello".


Some More Exercises

::: {.callout-note}

Celsius to Fahrenheit

Write a function called celsius_to_fahrenheit(celsius).

Use this formula:

(F = C + 32)

The function should return the temperature in Fahrenheit.

Test it with:

  • 0
  • 20
  • 37
  • 100 :::
def celsius_to_fahrenheit(celsius):
    return celsius * 9 / 5 + 32

print(celsius_to_fahrenheit(0))
print(celsius_to_fahrenheit(20))
print(celsius_to_fahrenheit(37))
print(celsius_to_fahrenheit(100))
32.0
68.0
98.6
212.0
Password Checker

Write a function called check_password(password).

It should return True if the password is "python".

Otherwise, it should return False.

Then ask the user for a password and use the function inside an if statement.

def check_password(password):
    return password == "python"

password = input("Password: ")

if check_password(password):
    print("Access granted")
else:
    print("Incorrect password")
StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.
Grade Classifier

Write a function called classify_grade(grade).

It should return:

  • "Fail" if the grade is below 5
  • "Pass" if the grade is from 5 to below 7
  • "Good" if the grade is from 7 to below 9
  • "Excellent" if the grade is 9 or higher

Then test the function with several grades.

def classify_grade(grade):
    if grade < 5:
        return "Fail"
    elif grade < 7:
        return "Pass"
    elif grade < 9:
        return "Good"
    else:
        return "Excellent"

print(classify_grade(3))
print(classify_grade(5))
print(classify_grade(8))
print(classify_grade(10))
Fail
Pass
Good
Excellent
Text Analyzer

Write a function called analyze_text(text).

It should print:

  • the original text
  • the number of characters
  • the text in uppercase
  • the first character
  • the last character

Example:

analyze_text("python")
def analyze_text(text):
    print(f"Original text: {text}")
    print(f"Number of characters: {len(text)}")
    print(f"Uppercase: {text.upper()}")
    print(f"First character: {text[0]}")
    print(f"Last character: {text[-1]}")

analyze_text("python")
Original text: python
Number of characters: 6
Uppercase: PYTHON
First character: p
Last character: n

Homework and Review

When you feel ready, you can test your knowledge by working through the review exercises.

Work through the “Functions” homework exercises available here. To earn participation credit, you must complete the exercises highlighted in red.

Homework