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. *argscollects extra positional arguments (given without a name) into a tuple, and**kwargscollects 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*argsare keyword-only:def click(locator, *, timeout=10)forces callers to writetimeout=. - 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
| Status | Question | Difficulty | Asked in |
|---|---|---|---|
| Not started | A list as a default argument | Easy-Medium | |
| Not started | Is Python pass by value or pass by reference? | Medium | Asked in: Infosys |
| Pro+ | Why are mutable default arguments a bug? | Medium | |
| Pro+ | *args, **kwargs and keyword-only parameters | Easy-Medium | Asked in: TCS |
| Pro+ | Two functions with the same name | Medium | Asked in: Accenture, Capgemini, Infosys +2 |
| Pro+ | Returning several values, or none | Easy | |
| Pro+ | Lambdas created in a loop | Medium | |
| Pro+ | Positional-only (/) and keyword-only (*) parameters | Medium | |
| Pro+ | Passing a function versus calling it | Easy-Medium |
The first 2 questions of each topic are free. Pro+ opens every question.