Python Lesson 116 – Functions | Dataplexa

Functions in Python

In this lesson you will learn about Functions — one of the most important concepts in all of programming. Functions let you write a block of code once and use it as many times as you need. By the end of this lesson you will be writing clean, reusable, professional-quality code using functions.


1. What is a Function?

A function is a named block of code that performs a specific task. Instead of writing the same logic over and over in different places, you write it once inside a function and simply call the function by name whenever you need it.

Think of a function like a coffee machine. You press a button (call the function), the machine does its job, and you get coffee (the result). You do not need to understand every internal step — you just use it. And you can press the button as many times as you want.

Why do functions exist?

  • Reusability — write once, use anywhere in your program
  • Readability — code with functions is easier to read and understand
  • Organisation — break a big program into small, manageable pieces
  • Easy to fix — if there is a bug, you fix it in one place, not in 10 places

Where are functions used? Every real Python program uses functions. Web frameworks, data science scripts, automation tools, games — all of them are built from functions. From Lesson 16 onwards, every program you write will use functions.


2. Defining and Calling a Function

You define a function using the def keyword, followed by the function name, parentheses, and a colon. The code inside the function is indented. To run the function, you call it by writing its name followed by parentheses.

# DEFINE the function — this is the blueprint
def greet():
    print("Hello! Welcome to Python.")
    print("Functions make code reusable.")
# CALL the function — this runs the code inside it
greet()
# You can call the same function multiple times
greet()
greet()
Hello! Welcome to Python.
Functions make code reusable.
Hello! Welcome to Python.
Functions make code reusable.
Hello! Welcome to Python.
Functions make code reusable.

What just happened?

  • The def greet(): line defines the function — it tells Python "remember this block of code under the name greet".
  • The function does not run when it is defined. It only runs when you call it with greet().
  • Calling it three times prints the output three times — the same two lines repeated. This is the core power of functions.

3. Functions with Parameters

A parameter is a variable you place inside the parentheses when defining a function. It lets you pass information into the function so it can work with different data each time it is called. The value you pass when calling the function is called an argument.

# "name" is the parameter — it receives the value when the function is called
def greet_user(name):
    print(f"Hello, {name}! Good to see you.")
# "Priya", "Kiran", "Arjun" are the arguments — the actual values passed in
greet_user("Priya")
greet_user("Kiran")
greet_user("Arjun")
# Function with two parameters
def add_numbers(a, b):
    print(f"{a} + {b} = {a + b}")
add_numbers(10, 5)
add_numbers(100, 250)
Hello, Priya! Good to see you.
Hello, Kiran! Good to see you.
Hello, Arjun! Good to see you.
10 + 5 = 15
100 + 250 = 350

What just happened?

  • Each time greet_user() is called, the argument replaces the parameter name inside the function — so "Priya", "Kiran", and "Arjun" each get their own personalised message.
  • add_numbers(a, b) takes two parameters — both are used in the calculation. The order matters: the first argument goes to a, the second to b.
  • Parameter = the placeholder in the definition. Argument = the real value you pass when calling.

4. Returning Values from a Function

So far our functions only print results. But in real programs, you usually want a function to calculate something and give the result back so you can use it elsewhere. This is done with the return statement. Once Python hits return, it exits the function and sends the value back to wherever the function was called.

# This function RETURNS the result instead of printing it
def multiply(a, b):
    result = a * b
    return result          # send the value back to the caller
# The returned value is stored in a variable
answer = multiply(6, 7)
print("6 x 7 =", answer)
# You can also use the returned value directly
print("3 x 9 =", multiply(3, 9))
# Practical example — calculate the final price after discount
def final_price(price, discount_percent):
    discount = price * discount_percent / 100
    return price - discount
cost = final_price(2000, 15)    # 15% off ₹2000
print("Final price after 15% off:", cost)
6 x 7 = 42
3 x 9 = 27
Final price after 15% off: 1700.0

What just happened?

  • return result sends the value 42 back to the line answer = multiply(6, 7) — the variable answer now holds 42.
  • You can also use a returned value directly inside print() without storing it — print(multiply(3, 9)) calls the function and prints what it returns.
  • The final_price function is a real-world example — it takes a price and discount, calculates, and returns the result. This kind of function is used in every shopping or billing application.

5. Default Parameter Values

You can give a parameter a default value so that the function still works even if the caller does not provide that argument. If an argument is passed, it overrides the default. If not, the default is used.

# "country" has a default value of "India"
def register_user(name, country="India"):
    print(f"Name: {name} | Country: {country}")
# Called with both arguments — default is overridden
register_user("Priya", "USA")
# Called with only one argument — default is used for country
register_user("Kiran")
register_user("Arjun", "Canada")
Name: Priya | Country: USA
Name: Kiran | Country: India
Name: Arjun | Country: Canada

What just happened?

  • When register_user("Kiran") is called with only one argument, Python uses "India" as the default value for country.
  • When two arguments are provided, the default is completely replaced by the actual argument.
  • Default parameters must always come after non-default parameters in the function definition — def f(a, b="x") is valid, but def f(a="x", b) is not.

6. Keyword Arguments

Normally you pass arguments in the same order as the parameters. But with keyword arguments, you can pass them in any order by specifying the parameter name. This makes function calls much clearer, especially when a function has many parameters.

def create_profile(name, age, city):
    print(f"Name: {name} | Age: {age} | City: {city}")
# Normal way — order must match exactly
create_profile("Sneha", 25, "Mumbai")
# Keyword arguments — order does not matter
create_profile(age=30, city="Delhi", name="Rahul")
# Mix: positional first, then keyword
create_profile("Meera", city="Pune", age=22)
Name: Sneha | Age: 25 | City: Mumbai
Name: Rahul | Age: 30 | City: Delhi
Name: Meera | Age: 22 | City: Pune

What just happened?

  • In the second call, the arguments are in a completely different order but Python matches them correctly because each one is labelled with the parameter name.
  • In the third call, "Meera" is positional (goes to name) and the remaining two use keyword style. Positional arguments must always come before keyword arguments.
  • Keyword arguments make code more readable — when you see city="Pune" in a function call, it is instantly clear what that value is for.

7. *args — Accepting Any Number of Arguments

Sometimes you do not know in advance how many arguments will be passed to a function. Using *args lets a function accept any number of positional arguments. All the values are collected into a tuple inside the function.

# *args collects all extra arguments into a tuple
def add_all(*numbers):
    total = 0
    for n in numbers:
        total += n
    return total
# Call with any number of arguments
print(add_all(5, 10))
print(add_all(1, 2, 3, 4, 5))
print(add_all(100, 200, 300, 400))
# Another example — greet multiple people at once
def greet_all(*names):
    for name in names:
        print(f"Hello, {name}!")
greet_all("Priya", "Kiran", "Arjun")
15
15
1000
Hello, Priya!
Hello, Kiran!
Hello, Arjun!

What just happened?

  • *numbers collects all passed arguments into a tuple — so add_all(1, 2, 3, 4, 5) gives numbers = (1, 2, 3, 4, 5) inside the function.
  • The loop then adds them all up. This works whether you pass 2 arguments or 20.
  • The name args is just a convention — you can write *values or *items, but *args is the standard style everyone recognises.

8. **kwargs — Accepting Any Number of Keyword Arguments

**kwargs lets a function accept any number of keyword arguments. All the name-value pairs are collected into a dictionary inside the function. This is very useful when building flexible functions that handle optional or variable configuration data.

# **kwargs collects all keyword arguments into a dictionary
def show_details(**info):
    for key, value in info.items():
        print(f"  {key}: {value}")
# Call with any keyword arguments you want
print("--- User 1 ---")
show_details(name="Priya", age=25, city="Mumbai")
print("--- User 2 ---")
show_details(name="Kiran", country="India", plan="Premium", active=True)
--- User 1 ---
  name: Priya
  age: 25
  city: Mumbai
--- User 2 ---
  name: Kiran
  country: India
  plan: Premium
  active: True

What just happened?

  • **info collects every keyword argument into a dictionary — so name="Priya", age=25 becomes {"name": "Priya", "age": 25} inside the function.
  • The two calls pass completely different keyword arguments — different keys, different number of keys. The function handles both perfectly.
  • **kwargs is used heavily in real frameworks like Django and Flask to handle flexible configuration and form data.

9. Returning Multiple Values

A Python function can return more than one value at the same time. The values are returned as a tuple and can be unpacked into separate variables on the same line. This is something many other programming languages cannot do as cleanly.

# Function that returns three values at once
def get_stats(numbers):
    total   = sum(numbers)
    average = total / len(numbers)
    highest = max(numbers)
    return total, average, highest    # returns a tuple of three values
marks = [72, 85, 90, 60, 88]
# Unpack all three returned values into separate variables
total, avg, top = get_stats(marks)
print("Total   :", total)
print("Average :", avg)
print("Highest :", top)
Total : 395
Average : 79.0
Highest : 90

What just happened?

  • return total, average, highest packs three values into a tuple and sends them all back at once.
  • total, avg, top = get_stats(marks) unpacks that tuple into three separate variables in one line.
  • This is cleaner than writing three separate functions or using a dictionary — it is the standard Python pattern for functions that produce multiple related results.

10. Variable Scope — Local vs Global

Scope means where in your code a variable can be accessed. A variable created inside a function is called a local variable — it only exists while the function is running. A variable created outside all functions is a global variable — it can be accessed anywhere.

app_name = "DataPlexa"    # global variable — lives outside any function
def show_info():
    version = "2.0"       # local variable — only exists inside this function
    print("App    :", app_name)   # can access global variable ✓
    print("Version:", version)    # can access local variable ✓
show_info()
# Global variable is accessible here
print("App name outside function:", app_name)
# Local variable is NOT accessible outside the function
try:
    print(version)        # this will fail
except NameError as e:
    print("Error:", e)
App : DataPlexa
Version: 2.0
App name outside function: DataPlexa
Error: name 'version' is not defined

What just happened?

  • app_name is global — it is visible both inside and outside the function.
  • version is local to show_info() — once the function finishes, version no longer exists. Trying to access it outside raises a NameError.
  • This is an important safety feature — local variables cannot accidentally interfere with the rest of your program. Always prefer local variables inside functions unless you specifically need a global one.

11. Documenting Functions with Docstrings

A docstring is a short description written right inside a function to explain what it does. It is placed as the very first line inside the function, using triple quotes. Docstrings are best practice in professional Python — they make your functions self-explanatory for other developers (and future you).

def calculate_tax(income, rate):
    """
    Calculates the tax amount for a given income and tax rate.
    income : the total income (float or int)
    rate   : the tax rate as a percentage (e.g. 20 for 20%)
    Returns: the tax amount as a float
    """
    return income * rate / 100
# You can read the docstring using help() or __doc__
print(calculate_tax.__doc__)
# Using the function
tax = calculate_tax(50000, 20)
print("Tax owed:", tax)

    Calculates the tax amount for a given income and tax rate.
    income : the total income (float or int)
    rate : the tax rate as a percentage (e.g. 20 for 20%)
    Returns: the tax amount as a float
    
Tax owed: 10000.0

What just happened?

  • The triple-quoted string right after the def line is the docstring — Python stores it as __doc__ on the function.
  • calculate_tax.__doc__ prints the full docstring — this is how tools like VS Code, Jupyter Notebook, and Python's help() system display function documentation.
  • Always write a docstring for any function someone else might use — it takes 30 seconds and saves hours of confusion.

12. Real-World Example — Invoice Generator

This program uses multiple functions together to build a simple invoice calculator. Each function does one job — this is the correct way to structure real programs.

# --- Individual functions, each doing one job ---
def calculate_subtotal(price, quantity):
    """Returns the subtotal before tax."""
    return price * quantity
def calculate_tax(subtotal, rate=18):
    """Returns the tax amount. Default rate is 18%."""
    return subtotal * rate / 100
def calculate_total(subtotal, tax):
    """Returns the final total."""
    return subtotal + tax
def print_invoice(item, price, quantity):
    """Prints a full invoice for an item."""
    subtotal = calculate_subtotal(price, quantity)
    tax      = calculate_tax(subtotal)
    total    = calculate_total(subtotal, tax)
    print(f"  Item     : {item}")
    print(f"  Price    : {price}")
    print(f"  Quantity : {quantity}")
    print(f"  Subtotal : {subtotal}")
    print(f"  Tax (18%): {tax}")
    print(f"  Total    : {total}")
# --- Call the main function ---
print("===== INVOICE =====")
print_invoice("Wireless Mouse", 799, 3)
print("===================")
===== INVOICE =====
  Item : Wireless Mouse
  Price : 799
  Quantity : 3
  Subtotal : 2397
  Tax (18%): 431.46
  Total : 2828.46
===================

What just happened?

  • Each small function does exactly one calculation — this is called the single responsibility principle and it is the foundation of professional coding.
  • print_invoice() calls the three helper functions and combines their results — functions calling other functions is very normal and powerful.
  • If the tax rate ever changes, you only update it in calculate_tax() — one place, not throughout the whole program. This is why functions save so much time in real projects.

13. Lesson Summary

Concept Syntax / Example What It Does
Define a function def greet(): Creates a reusable block of code
Call a function greet() Runs the code inside the function
Parameters def greet(name): Accepts input values into the function
Return value return result Sends a value back to the caller
Default parameter def f(x, y=10): Used when argument is not provided
Keyword argument f(y=5, x=2) Pass arguments in any order by name
*args def f(*args): Accepts any number of positional args as a tuple
**kwargs def f(**kwargs): Accepts any number of keyword args as a dict
Multiple return return a, b, c Returns multiple values as a tuple
Local variable x = 5 (inside function) Only exists inside that function
Global variable x = 5 (outside function) Accessible everywhere in the file
Docstring """description""" Documents what the function does

🧪 Practice Questions

Answer based on what you learned in this lesson.

1. What keyword is used to define a function in Python?




2. Which keyword sends a value back from a function to the caller?




3. When you use *args, all the extra positional arguments are collected into a __________.




4. What error is raised when you try to access a local variable outside its function?




5. When you use **kwargs, all the keyword arguments are collected into a __________.



🎯 Quiz — Test Your Understanding

Q1. When does the code inside a function actually run?







Q2. What happens when you call a function without providing a value for a default parameter?







Q3. How do you return two values a and b from a function?







Q4. What is a local variable?







Q5. Which syntax collects any number of keyword arguments into a dictionary?