Functions: Return, Scope & Flow
Returning values
The return statement sends a value back to the caller and ends the function immediately. A function can return one value, several values (as a tuple) or nothing.
def greet(name): # void function: no return
print('Hello', name)
def square(n): # returns one value
return n * n
def min_max(seq): # returns many values as a tuple
return min(seq), max(seq)
r = greet('Asha')
print(r, square(9), min_max([7, 2, 9]))
low, high = min_max([7, 2, 9])
print(low, high)def check(n):
if n % 2 == 0:
return 'even'
print('never printed') # unreachable after return
return 'odd'
print(check(4), check(7))Void functions and None
A function without return (or with a bare return) is called a void function; it returns None. Board questions often print the result of such a function — the answer includes None.
Flow of execution
Python executes top to bottom. A def only defines the function. When a call is reached, control jumps into the function, runs its body, and returns to the line after the call.
def add(a, b): # line 1
c = a + b # line 2
return c # line 3
def show(): # line 5
print('Start') # line 6
s = add(3, 4) # line 7
print('Sum', s) # line 8
show() # line 10
print('End') # line 11Scope of variables (LEGB)
x = 'global'
def outer():
x = 'enclosing'
def inner():
print(x) # not local -> enclosing found first
inner()
outer()
print(x)The global keyword
Reading a global variable inside a function is allowed. To assign to it, declare it global.
count = 10
def double():
global count # refer to the global variable
count = count * 2
double()
double()
print(count)UnboundLocalError
If a function assigns to a name anywhere in its body, that name is local in the whole function. Reading it before the assignment is an error:
c = 10
def g():
print(c) # c is local here because of the next line
c = 5
g()UnboundLocalError: cannot access local variable 'c' where it is not associated with a value
(Older Python versions say: local variable 'c' referenced before assignment.) Fix: use global c or a different name.
Passing mutable and immutable objects
def update(L, n):
L.append(n) # changes the caller's list (mutable)
n = n + 1 # new local int; caller's int unchanged
L = [0] # rebinds local name only
return n
a = [1]
b = 5
print(update(a, b), a, b)def increment(x):
x = x + 1
return x
def append_item(lst):
lst += [99]
n = 5
increment(n)
data = [1, 2]
append_item(data)
print(n, data)Immutable argument (int, str, tuple)
- Function gets a reference to the same object
- Any "change" creates a new local object
- Caller’s value is unchanged
Mutable argument (list, dict)
- Function gets a reference to the same object
- append, +=, d[k]=v change it in place
- Caller sees the change
Board-style output questions
def fun(s):
k = len(s)
m = ''
for i in range(0, k):
if s[i].isupper():
m = m + s[i].lower()
elif s[i].isalpha():
m = m + s[i].upper()
else:
m = m + 'bb'
print(m)
fun('school2@com')Trace: lowercase letters become uppercase, uppercase become lowercase, and every non-letter (2 and @) becomes "bb".
def f(x, y=[]): y.append(x); return y → print(f(1), f(2)). Why? (Default lists are created once.)
Answer: [1, 2] [1, 2] — both calls share the same default list, which is printed after both appends.
Summary
- return ends the function; return a, b gives a tuple; no return → None.
- Flow jumps into the function at the call and comes back.
- LEGB lookup; use global to assign to a module-level name.
- Mutable arguments can be changed in place; immutable cannot.
