Skip to content
CodeKruPro

Your language · Python

Methods and functions

Parameters, return values, overloading or default arguments, and passing values.

Learn: Functions in Python: arguments, defaults and closures8 minute read

A function is a named block of code that takes inputs and returns a result. Python functions are flexible: they accept default values, keyword arguments and any number of extra arguments, and they can be passed around like any other value. Interviewers use that flexibility to set traps. The best known is the mutable default argument (a default value that can change, such as a list), which can make a test helper leak data from one test into the next. An argument is the value you pass in a call; a parameter is the name in the function definition.

Step 1: defining and calling

def creates a function object and binds it to a name (attaches the name to the object). A function with no return statement returns None. return a, b returns a tuple, which the caller can unpack: status, body = fetch(). You can pass arguments by position or by name, as in login(user="asha", password=pw). Named (keyword) arguments make calls easy to read.

Step 2: the kinds of parameters

  • A default value, as in def wait_for(locator, timeout=10). Parameters with defaults come after those without.
  • *args collects extra positional arguments (given without a name) into a tuple, and **kwargs collects extra keyword arguments (given with a name) into a dict. Wrappers (functions that call another function) use both to pass everything through.
  • Parameters after * or *args are keyword-only: def click(locator, *, timeout=10) forces callers to write timeout=.
  • Parameters before / are positional-only (Python 3.8+).

Python has no overloading by signature: a second def with the same name replaces the first. Defaults and *args cover the same needs.

def report(name, status="PASS", *tags, duration=0.0, **extra):
    return f"{name}: {status} tags={tags} duration={duration} extra={extra}"

print(report("login"))
print(report("search", "FAIL", "smoke", "ui", duration=1.5, browser="chrome"))

def stats(values):
    return min(values), max(values)

low, high = stats([3, 9, 1])
print(low, high)

def nothing():
    pass

print(nothing())

def greet(name):
    return "hi " + name

def greet(name, greeting):
    return greeting + " " + name

try:
    greet("Asha")
except TypeError as e:
    print("TypeError:", e)
login: PASS tags=() duration=0.0 extra={}
search: FAIL tags=('smoke', 'ui') duration=1.5 extra={'browser': 'chrome'}
1 9
None
TypeError: greet() missing 1 required positional argument: 'greeting'

Step 3: the mutable default trap

Python works out a default value once, when the def line runs, and stores it on the function. So a list or dict used as a default is one shared object, and every call that relies on the default changes that same object. The fix is to use None as the default and create the list inside the function.

def add_tag(tag, tags=[]):
    tags.append(tag)
    return tags

print(add_tag("smoke"), add_tag("ui"))

def add_tag_fixed(tag, tags=None):
    if tags is None:
        tags = []
    tags.append(tag)
    return tags

print(add_tag_fixed("smoke"), add_tag_fixed("ui"))
['smoke', 'ui'] ['smoke', 'ui']
['smoke'] ['ui']

Step 4: how arguments are passed

Python passes a reference to each argument object, so the parameter is a new name for the caller's object. If the function changes a mutable object, for example by appending to a list, the caller sees it. If it rebinds the parameter, as in items = [], only the local name moves. Ints and strings cannot change, so for them it looks like pass by value (the function works on its own copy). This model is called "call by sharing".

Step 5: functions are objects, and closures

You can store functions in variables and dicts, pass them as arguments and return them. wait.until(page_loaded) (a Selenium wait that keeps checking a condition until it is true or time runs out) passes the function itself, so the wait can call it again and again; wait.until(page_loaded(driver)) would pass one result. A lambda is a small function with no name, limited to one expression (see Lambdas and comprehensions).

A function defined inside another function can use the outer function's variables. This is a closure. To assign to such a variable, declare it nonlocal (it tells Python to use the outer function's variable). A closure captures the variable, not its value at that moment, so lambdas made in a loop all see the loop variable's final value unless you bind it with a default.

def make_counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

next_id = make_counter()
next_id()
print(next_id(), next_id())

late = [lambda: i for i in range(3)]
bound = [lambda i=i: i for i in range(3)]
print([f() for f in late], [f() for f in bound])

actions = {"open": lambda: "opened", "close": lambda: "closed"}
print(actions["close"]())
2 3
[2, 2, 2] [0, 1, 2]
closed

How to answer in an interview

For the mutable default question, give the cause, the effect and the fix in three sentences: "Defaults are created once, when def runs. A list default is shared by every call, so items pile up. I use None and create the list inside." For pass by value or reference, say "pass by object reference", then contrast lst.append(4), which the caller sees, with lst = [0], which it does not.

Common mistakes

  • Using [] or {} as a default value.
  • Writing two functions with the same name and expecting overloading.
  • Calling a function where you meant to pass it, as in wait.until(check()).
  • Expecting lambdas made in a loop to remember each loop value.

Recap

Functions return None by default and can return several values as a tuple. *args is a tuple and **kwargs a dict. Defaults are evaluated once, so never use a mutable one. Arguments are passed by object reference: changes to a mutable object are visible, rebinding is not. Functions are values, and closures capture variables.

9 questions0/9 solved

Not startedNot startedIn progressIn progressSolvedSolvedPro+Pro+

The first 2 questions of each topic are free. Pro+ opens every question.

Back to the plan