Default Arguments, *args/**kwargs, and Variable Scope
📂 Phase 2: Loops, Conditional Statements, Functions (Days 6–10) · Python ProgrammingPython 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
| Scope | Where Declared | Accessible From |
|---|---|---|
| Local | Inside a function | Only within that function |
| Global | Outside any function, at module level | Anywhere 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.