Python Functions Explained: Arguments, Scope and Common Traps
A practical tour of how Python functions receive arguments, resolve variable names and hold on to state, with the mistakes that catch most learners.
SmartCampus Buddy TeamSeptember 15, 20267 min read
A function is a named, reusable block of code. In Python it is also an object, which means you can pass it around, store it in a list or return it from another function. Most bugs with functions come from three places: default arguments, variable scope and closures.
Parameters and arguments
Python supports positional arguments, keyword arguments, defaults, and two collectors: *args gathers extra positional arguments into a tuple, and **kwargs gathers extra keyword arguments into a dict.
def report(title, *scores, **options):
print(title, scores, options)
report("Quiz", 8, 9, sep="-") # Quiz (8, 9) {'sep': '-'}The mutable default trap
Default values are evaluated once, when the function is defined, not on every call. A mutable default such as a list is therefore shared between calls.
def add(item, bucket=[]):
bucket.append(item)
return bucketThe safe pattern is to use None as the default and create the list inside the function:
def add(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucketScope: where names are looked up
Python resolves a name by checking the local scope, then any enclosing function scopes, then the module (global) scope, then built-ins. Assigning to a name inside a function makes it local for the whole function, which is why count += 1 on a global raises UnboundLocalError. Prefer passing values in and returning results over reaching for global.
Closures and late binding
A closure remembers variables from the scope where it was created. It captures the variable, not its value at that moment, so closures created in a loop all see the final loop value unless you bind it, for example with a default argument (lambda x, i=i: x * i).
Key takeaways
- Never use a mutable object as a default value; use None and create it inside.
- Assignment inside a function creates a local name.
- Closures capture variables, not values.
- Give functions a single clear job and return values instead of relying on side effects.