🐍
Syllabus / Python Programming / Phase 2: Loops, Conditional Statements, Functions (Days 6–10)
Intermediate

Default Arguments, *args/**kwargs, and Variable Scope

📂 Phase 2: Loops, Conditional Statements, Functions (Days 6–10) · Python Programming

Python functions can be made far more flexible than the simple fixed-parameter functions seen so far — through default values, accepting an unknown number of arguments, and understanding exactly where a variable can and cannot be accessed.

Default Arguments

A default argument supplies a fallback value that is used automatically when the caller doesn't provide one.

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

greet("Vishwas")              # Hello, Vishwas!  — uses the default
greet("Priya", "Welcome")     # Welcome, Priya!  — overrides the default
Rule: parameters without a default value must come before parameters that have one in the function definition. def greet(greeting="Hello", name) would raise a SyntaxError.

Keyword Arguments

You can pass arguments by explicitly naming the parameter, which frees you from having to remember the exact positional order.

def describe_pet(animal, name):
    print(f"I have a {animal} named {name}")

describe_pet(animal="dog", name="Buddy")
describe_pet(name="Buddy", animal="dog")   # order doesn't matter with keywords

*args — Accepting Any Number of Positional Arguments

When you don't know in advance how many positional arguments will be passed, prefixing a parameter with * collects them all into a tuple.

def total(*numbers):
    return sum(numbers)

print(total(1, 2, 3))        # 6
print(total(10, 20, 30, 40)) # 100

**kwargs — Accepting Any Number of Keyword Arguments

Prefixing a parameter with ** collects any number of named keyword arguments into a dictionary.

def print_profile(**details):
    for key, value in details.items():
        print(f"{key}: {value}")

print_profile(name="Vishwas", role="Developer", city="Bengaluru")
# name: Vishwas
# role: Developer
# city: Bengaluru

Combining Everything in One Function Signature

def build_profile(name, *hobbies, **extra_info):
    print(f"Name: {name}")
    print(f"Hobbies: {hobbies}")
    print(f"Extra info: {extra_info}")

build_profile("Vishwas", "coding", "reading", city="Bengaluru", role="Developer")

The required order in a function signature is always: standard parameters, then *args, then **kwargs.

Variable Scope: Local vs Global

ScopeWhere DeclaredAccessible From
LocalInside a functionOnly within that function
GlobalOutside any function, at module levelAnywhere in the file, including inside functions (read-only by default)
x = 10   # global variable

def show_x():
    print(x)   # can READ the global variable freely

show_x()   # 10

Why Assigning to a Global Variable Inside a Function Needs global

count = 0

def increment():
    count += 1   # ERROR — UnboundLocalError
    # Python treats count as a NEW local variable the moment
    # you assign to it inside the function, unless told otherwise

def increment_fixed():
    global count
    count += 1   # now this correctly modifies the global variable

increment_fixed()
print(count)   # 1
This is one of the most common beginner bugs: simply reading a global variable works fine without any keyword, but assigning to a variable with the same name inside a function silently creates a separate local variable instead — unless you explicitly declare global first.

Mutable Default Arguments — A Classic Gotcha

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

print(add_item("apple"))   # ['apple']
print(add_item("banana"))  # ['apple', 'banana'] — NOT a fresh empty list!

# SAFE — create a new list inside the function instead
def add_item_safe(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items
Interview tip: "Why are mutable default arguments dangerous in Python?" is an extremely common intermediate question — default argument values are evaluated exactly once, when the function is defined, not each time it is called, so a mutable default like a list persists and accumulates state across every call that doesn't override it.