Topic 04 / 15

Functions — Arguments, Returns & Scope

~10 min read  //  Python Series  //  Coding India

Defining Functions

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

message = greet("Digamber")
print(message)   # Hello, Digamber!

A function without an explicit return returns None. Functions are values too — you can pass them around like any other object.

Default & Keyword Arguments

def connect(host, port=5432, timeout=10):
    ...

connect("localhost")                  # uses both defaults
connect("localhost", 3306)            # positional
connect("localhost", timeout=30)      # keyword — skip the middle arg

The classic trap: never use a mutable default. It’s created once and shared between calls:

# WRONG — the same list is reused on every call
def add_item(item, items=[]):
    items.append(item)
    return items

# RIGHT
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

*args and **kwargs

Accept any number of positional or keyword arguments:

def log(*args, **kwargs):
    print("positional:", args)     # a tuple
    print("keyword:", kwargs)      # a dict

log(1, 2, 3, level="info", user="ravi")
# positional: (1, 2, 3)
# keyword: {'level': 'info', 'user': 'ravi'}

The same stars unpack when calling:

coords = (28.6, 77.2)
point(*coords)              # point(28.6, 77.2)
config = {"host": "db", "port": 5432}
connect(**config)           # connect(host="db", port=5432)

Returning Multiple Values

def min_max(numbers):
    return min(numbers), max(numbers)   # returns a tuple

low, high = min_max([3, 1, 4, 1, 5])

Scope — the LEGB Rule

Python resolves names in order: Local → Enclosing → Global → Built-in. Reading outer variables works automatically; assigning to them creates a new local unless you say otherwise:

count = 0

def increment():
    global count        # required to ASSIGN to a module-level name
    count += 1

Needing global is usually a design smell — prefer returning values or using a class.

Type Hints — Modern Python Style

Hints don’t change runtime behaviour, but editors and type checkers (mypy, pyright) use them to catch bugs before you run anything:

def split_bill(total: float, people: int) -> float:
    return round(total / people, 2)

def find_user(user_id: int) -> "User | None":
    ...

Professional Python in 2026 is typed Python. Start the habit now.