Difficulty: Beginner
Explain Python functions, argument types, scope rules, and first-class function concepts.
Functions in Python are first-class objects - they can be assigned to variables, passed as arguments, and returned from other functions.
Argument types: - Positional: func(a, b) - Keyword: func(name='Alice') - Default: def func(x=10) - *args: variable positional arguments (tuple) - kwargs: variable keyword arguments (dict)
Scope follows LEGB rule: Local -> Enclosing -> Global -> Built-in.
Mutable default argument is a common Python gotcha - default values are evaluated once at function definition, not at each call.
# All argument types in one function
def example(a, b, c=10, *args, kwargs):
print(f"a={a}, b={b}, c={c}")
print(f"args={args}")
print(f"kwargs={kwargs}")
example(1, 2, 3, 4, 5, name='Alice', age=30)
# GOTCHA: Mutable default argument
def append_to(element, target=[]): # BUG!
target.append(element)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] - NOT [2]!
# Fix: use None as default
def append_to_fixed(element, target=None):
if target is None:
target = []
target.append(element)
return target
print(append_to_fixed(1)) # [1]
print(append_to_fixed(2)) # [2] - correct!
Default mutable arguments are shared across calls because they are created once at function definition time. Always use None as default for mutable types.
# Functions as arguments
def apply(func, value):
return func(value)
print(apply(str.upper, 'hello')) # 'HELLO'
print(apply(len, [1, 2, 3])) # 3
# Functions as return values (closure)
def multiplier(factor):
def multiply(x):
return x * factor # 'factor' is captured
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# Storing functions in data structures
operations = {
'+': lambda a, b: a + b,
'-': lambda a, b: a - b,
'*': lambda a, b: a * b,
'/': lambda a, b: a / b,
}
print(operations['+'](10, 3)) # 13
print(operations['*'](10, 3)) # 30
Functions being first-class means they're just objects. Closures capture variables from the enclosing scope, enabling powerful patterns like factory functions.
x = 'global'
def outer():
x = 'enclosing'
def inner():
x = 'local'
print(f"inner: {x}") # local
inner()
print(f"outer: {x}") # enclosing
outer()
print(f"global: {x}") # global
# Using global and nonlocal
counter = 0
def increment():
global counter
counter += 1
def make_counter():
count = 0
def increment():
nonlocal count # Refers to enclosing scope variable
count += 1
return count
return increment
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
LEGB: Local, Enclosing, Global, Built-in. Use 'global' to modify module-level variables, 'nonlocal' to modify enclosing function variables.
Functions, Arguments, Default Values, Scope, First-Class Functions, Closures