
Python Course
Functions in Python
A function is a named block of code that performs a specific task. Instead of writing the same logic in multiple places throughout your program, you write it once inside a function and simply call it 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 work, 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.
Functions are the single most important concept you will learn as you move into intermediate Python. Every real program — web apps, data science scripts, automation tools, games — is built from functions. From this lesson onwards, every program you write will use them.
Why Functions Exist
- Reusability — write the logic once, use it anywhere in the program without repeating code.
- Readability — a well-named function makes code read almost like plain English.
- Organisation — break a large program into small, focused, manageable pieces.
- Easy to fix — if there is a bug, you fix it in one place, not in 10 different places across the file.
- Testability — small functions are easy to test individually before wiring together into a full program.
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 must be indented. To run the function, you call it by writing its name followed by parentheses.
# DEFINE — creates the blueprint (does not run yet)
def greet():
print("Hello! Welcome to Python.")
print("Functions make code reusable.")
# CALL — this actually runs the code inside
greet()
# Call the same function multiple times
greet()
greet()
- The
def greet():line defines the function — Python remembers it but does not run it yet. - The function only runs when you call it with
greet(). - Calling it three times prints the output three times — this is the core power of functions: write once, run everywhere.
Functions with Parameters
A parameter is a variable placed inside the parentheses in the function definition. It lets you pass information into the function so it can work with different data each time. The actual value you pass when calling is called an argument.
# "name" is the parameter — a placeholder for whatever gets passed in
def greet_user(name):
print(f"Hello, {name}! Good to see you.")
# "Priya", "Kiran", "Arjun" are the arguments — the actual values
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)
- Each time
greet_user()is called, the argument replaces the parametername— so each person gets their own personalised message. - With two parameters, the order matters — first argument goes to
a, second tob. - Parameter = the placeholder in the definition. Argument = the real value you pass when calling.
Returning Values from a Function
Functions that only print are useful, but in real programs you usually want a function to calculate something and give the result back so you can use it elsewhere. The return statement sends a value back to wherever the function was called. Once Python hits return, it exits the function immediately.
def multiply(a, b):
result = a * b
return result # sends the value back to the caller
# Store the returned value in a variable
answer = multiply(6, 7)
print("6 x 7 =", answer)
# Or use the returned value directly
print("3 x 9 =", multiply(3, 9))
# Real-world use: calculate 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 Rs.2000
print("Final price after 15% off:", cost)
return resultsends42back — the variableanswernow holds that value.- You can also use the returned value directly inside
print()without storing it first. - A function without a
returnstatement automatically returnsNone.
Default Parameter Values
You can give a parameter a default value so the function still works even if that argument is not provided. If an argument is passed, it overrides the default. If not, the default is used automatically.
# "country" has a default value of "India"
def register_user(name, country="India"):
print(f"Name: {name} | Country: {country}")
register_user("Priya", "USA") # default overridden
register_user("Kiran") # default used
register_user("Arjun", "Canada")
- When
register_user("Kiran")is called with only one argument, Python uses"India"as the default forcountry. - Default parameters must always come after non-default ones —
def f(a, b="x")is valid,def f(a="x", b)is not. - Default values are a great way to make functions flexible without requiring every caller to provide all details.
Keyword Arguments
Normally arguments are passed in the same order as the parameters. With keyword arguments, you can pass them in any order by specifying the parameter name. This makes function calls clearer, especially when there are many parameters.
def create_profile(name, age, city):
print(f"Name: {name} | Age: {age} | City: {city}")
# Positional — order must match exactly
create_profile("Sneha", 25, "Mumbai")
# Keyword — order does not matter
create_profile(age=30, city="Delhi", name="Rahul")
# Mix: positional first, then keyword
create_profile("Meera", city="Pune", age=22)
- In the second call, arguments are in a completely different order but Python matches each one correctly by name.
- Positional arguments must always come before keyword arguments in the same call.
- Keyword arguments make code more readable — seeing
city="Pune"in a call is instantly clear.
*args — Any Number of Positional Arguments
Sometimes you do not know in advance how many arguments will be passed. Using *args lets a function accept any number of positional arguments. Python collects all of them into a tuple inside the function.
def add_all(*numbers):
total = 0
for n in numbers:
total += n
return total
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
def greet_all(*names):
for name in names:
print(f"Hello, {name}!")
greet_all("Priya", "Kiran", "Arjun")
*numberscollects all arguments into a tuple —add_all(1, 2, 3, 4, 5)givesnumbers = (1, 2, 3, 4, 5)inside the function.- The loop then adds them up — this works whether you pass 2 or 20 arguments.
argsis just a convention — you could write*valuesor*items, but*argsis the universally recognised style.
**kwargs — 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 for flexible, configurable functions.
def show_details(**info):
for key, value in info.items():
print(f" {key}: {value}")
print("--- User 1 ---")
show_details(name="Priya", age=25, city="Mumbai")
print("--- User 2 ---")
show_details(name="Kiran", country="India", plan="Premium", active=True)
**infocollects all keyword arguments into a dictionary —name="Priya", age=25becomes{"name": "Priya", "age": 25}inside.- Both calls pass completely different keyword arguments — different keys, different counts. The function handles both perfectly.
**kwargsis used heavily in real frameworks like Django and Flask for handling flexible configuration and form data.
Returning Multiple Values
A Python function can return more than one value at once. The values are packed into a tuple and can be unpacked into separate variables on the calling line — something many other languages cannot do as cleanly.
def get_stats(numbers):
total = sum(numbers)
average = total / len(numbers)
highest = max(numbers)
return total, average, highest # returns a tuple of three
marks = [72, 85, 90, 60, 88]
# Unpack all three returned values at once
total, avg, top = get_stats(marks)
print("Total :", total)
print("Average :", avg)
print("Highest :", top)
return total, average, highestpacks all three into a tuple and sends them back at once.total, avg, top = get_stats(marks)unpacks the tuple into three separate variables in one line.- This is cleaner than writing three separate functions — the standard Python pattern for functions that produce multiple related results.
Variable Scope — Local vs Global
Scope means where in your code a variable can be accessed. A variable created inside a function is local — it only exists while the function runs. A variable created outside all functions is global — it is accessible anywhere in the file.
app_name = "Dataplexa" # global — lives outside any function
def show_info():
version = "2.0" # local — only exists inside this function
print("App :", app_name) # can access global ✓
print("Version:", version) # can access local ✓
show_info()
# Global still accessible here
print("Outside:", app_name)
# Local is NOT accessible outside the function
try:
print(version)
except NameError as e:
print("Error:", e)
app_nameis global — visible both inside and outside functions.versionis local toshow_info()— once the function finishes, it no longer exists. Trying to access it outside raises aNameError.- Local variables are a safety feature — they cannot accidentally interfere with the rest of your program. Always prefer local variables inside functions.
Documenting Functions with Docstrings
A docstring is a short description placed as the first line inside a function, written in triple quotes. It explains what the function does, what it expects, and what it returns. Docstrings are professional best practice — tools like VS Code, Jupyter, and Python's help() all read and display them.
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
# Read the docstring
print(calculate_tax.__doc__)
# Use the function
tax = calculate_tax(50000, 20)
print("Tax owed:", tax)
- The triple-quoted string right after the
defline is the docstring — Python stores it asfunction.__doc__. - Writing a docstring for any function others will use takes 30 seconds and saves hours of confusion.
- Always document: what the function does, what each parameter expects, and what it returns.
Real World Example — Invoice Generator
This program uses multiple functions together — each doing exactly one job. This is the correct way to structure real programs and is called the single responsibility principle.
def calculate_subtotal(price, quantity):
"""Returns 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 formatted 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}")
print("===== INVOICE =====")
print_invoice("Wireless Mouse", 799, 3)
print("===================")
- Each small function does exactly one calculation — easy to understand, test, and reuse.
print_invoice()calls the three helper functions — functions calling other functions is completely normal and very powerful.- If the tax rate ever changes, you update it in
calculate_tax()only — one change, entire program updated. This is why functions save hours in real projects.
Quick Reference Table
| Concept | Syntax | What It Does |
|---|---|---|
| Define | def greet(): | Creates a reusable block of code |
| Call | greet() | Runs the code inside the function |
| Parameters | def greet(name): | Accepts input values |
| 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): | Any number of positional args → tuple |
| **kwargs | def f(**kwargs): | Any number of keyword args → 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
What keyword is used to define a function in Python?
Which keyword sends a value back from a function to the caller?
When you use *args, all extra positional arguments are collected into a __________.
What error is raised when you try to access a local variable outside its function?
When you use **kwargs, all keyword arguments are collected into a __________.
Quick Quiz
When does the code inside a function actually run?
What happens when you call a function without providing a value for a default parameter?
How do you return two values a and b from a function?
What is a local variable?
Which syntax collects any number of keyword arguments into a dictionary?