Advanced Function Concepts

Learn about nested functions, closures, functions as first-class objects, and function documentation in Python.

Advanced Function Concepts

Python treats functions as first-class objects, meaning they can be assigned to variables, passed as arguments, returned from functions, and stored in data structures. This section explores these powerful concepts.

Functions as First-Class Objects

In Python, functions are objects just like integers, strings, or lists. This means you can:

Assign Functions to Variables

function-assignment.py
def greet(name):
return f"Hello, {name}!"
say_hello = greet # Assign function to variable
print(say_hello("Alice")) # Hello, Alice!

Pass Functions as Arguments

function-as-argument.py
def apply_operation(x, y, operation):
return operation(x, y)
def add(a, b):
return a + b
def multiply(a, b):
return a * b
print(apply_operation(5, 3, add)) # 8
print(apply_operation(5, 3, multiply)) # 15

Return Functions from Functions

return-function.py
def get_operation(operation_type):
def add(x, y):
return x + y
def subtract(x, y):
return x - y
if operation_type == "add":
return add
else:
return subtract
operation = get_operation("add")
print(operation(5, 3)) # 8

Store Functions in Data Structures

Functions can be stored in lists and dictionaries:

function-in-structures.py
def add(x, y):
return x + y
def sub(x, y):
return x - y
def mul(x, y):
return x * y
ops = [add, sub, mul]
print("Choose any operation", "0. Add", "1. Sub", "2. Mul", sep="\n")
operation = int(input())
num1 = int(input())
num2 = int(input())
res = ops[operation](num1, num2)
print(res)

Nested Functions

Functions can be defined inside other functions. These are called nested or inner functions:

nested-functions.py
def outer_function(x):
def inner_function(y):
return y * 2
return inner_function(x)
result = outer_function(5)
print(result) # 10

Why Use Nested Functions?

  1. Encapsulation: Hide implementation details
  2. Helper functions: Create utility functions specific to the outer function
  3. Closures: Create functions that remember their environment
nested-helper.py
def process_data(data, operation):
def validate(item):
return type(item) in (type(int), type(float))
def transform(item):
return operation(item)
validated = []
for item in data:
if validate(item):
validated.append(item)
transformed = []
for item in validated:
transformed.append(transform(item))
return transformed
numbers = [1, 2, "three", 4, 5.5]
result = process_data(numbers, lambda x: x * 2)
print(result) # [2, 4, 8, 11.0]

Closures

A closure is a nested function that remembers and has access to variables from its enclosing scope, even after the outer function has finished executing:

closure-basic.py
def outer_function(x):
def inner_function(y):
return x + y # x is from enclosing scope
return inner_function
add_five = outer_function(5)
print(add_five(3)) # 8
print(add_five(10)) # 15

How Closures Work

The inner function “closes over” the variables from the outer function’s scope:

closure-example.py
def create_multiplier(factor):
def multiplier(number):
return number * factor # factor is "captured" from outer scope
return multiplier
double = create_multiplier(2)
triple = create_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15

Some examples:

closure-counter.py
def create_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter1 = create_counter()
counter2 = create_counter()
print(counter1()) # 1
print(counter1()) # 2
print(counter2()) # 1 (independent counter)
print(counter1()) # 3
closure-config.py
def create_validator(min_value, max_value):
def validate(value):
if min_value <= value <= max_value:
return True
return False
return validate
validate_age = create_validator(0, 120)
validate_percentage = create_validator(0, 100)
print(validate_age(25)) # True
print(validate_age(150)) # False
print(validate_percentage(75)) # True
print(validate_percentage(150)) # False

Lambda Functions

A lambda is a small anonymous function which can take any number of arguments but can only have one expression as the body.

Syntax: lambda var1, var2, …: expression

lambda-basic.py
operations = {
'add': lambda x, y: x + y,
'subtract': lambda x, y: x - y,
'multiply': lambda x, y: x * y
}
result = operations['add'](3, 4)
print(result) # 7

Higher-Order Functions

Higher-order functions are functions that take other functions as arguments or return functions:

higher-order.py
def apply_twice(func, value):
"""Apply a function twice to a value"""
return func(func(value))
def square(x):
return x ** 2
result = apply_twice(square, 3)
print(result) # 81 (square(square(3)) = square(9) = 81)

Function Factories

Function factories create and return new functions:

function-factory.py
def create_power_function(exponent):
def power(base):
return base ** exponent
return power
square = create_power_function(2)
cube = create_power_function(3)
print(square(4)) # 16
print(cube(4)) # 64

Function Documentation

Docstrings

A docstring or documentation string is a multi-line string inserted on the first line of a function. It serves as documentation for the function. It’s what gets printed when we write help(fun). We can also access it using fun.__doc__:

docstring-example.py
def custom_max(x, y):
"""This function returns the maximum between 2 numbers"""
return x if x > y else y
print(custom_max(1, 2)) # 2
help(custom_max)

Docstrings are string literals that Python stores and can access at runtime, making them different from regular comments. They provide a way to document what a function does, how to use it, and what it returns.

Exercises

Exercise 1: Functions as Objects

Create a list of three functions: add, subtract, and multiply. Then call the function at index 1 with arguments 10 and 5.

Answer:
def add(x, y):
return x + y
def subtract(x, y):
return x - y
def multiply(x, y):
return x \* y
ops = [add, subtract, multiply]
result = ops[1](10, 5) # subtract(10, 5)
print(result) # 5

Exercise 2: Nested Functions

Write a function called outer that defines an inner function inner which prints a message. Call inner from within outer.

Answer:
def outer():
def inner():
print("This is the inner function")
inner()
outer() # This is the inner function

Exercise 3: Closures

Write a function called create_multiplier that takes a factor and returns a function that multiplies any number by that factor. Use it to create a function that doubles numbers. Read the factor and the number to multiply.

Closures

Checks: 0 times
Answer:
def create_multiplier(factor):
def multiplier(number):
return number * factor
return multiplier
factor = int(input())
number = int(input())
double = create_multiplier(factor)
print(double(number))

Exercise 4: Lambda Functions

Create a dictionary called operations with keys ‘add’, ‘subtract’, and ‘multiply’, where each value is a lambda function that performs that operation on two numbers. Then use it to calculate 10 + 5.

Answer:
operations = {
'add': lambda x, y: x + y,
'subtract': lambda x, y: x - y,
'multiply': lambda x, y: x * y
}
result = operations['add'](10, 5)
print(result) # 15

Exercise 5: Function Factory

Write a function called create_power_function that takes an exponent and returns a function that raises any number to that exponent. Read the exponent and base from input.

Function Factory

Checks: 0 times
Answer:
def create_power_function(exponent):
def power(base):
return base ** exponent
return power
exponent = int(input())
base = int(input())
power_func = create_power_function(exponent)
print(power_func(base))

Exercise 6: Writing a Docstring

Write a function called calculate_area that takes length and width as parameters, returns the area, and includes a docstring explaining what the function does.

Answer:
def calculate_area(length, width):
"""This function calculates and returns the area of a rectangle"""
return length * width
print(calculate_area(5, 3)) # 15

Exercise 7: Accessing Docstring

Given the following function, write code to access its docstring using both __doc__ and help().

def greet(name):
"""This function greets a person by name"""
print(f"Hello, {name}!")
Answer:
def greet(name):
"""This function greets a person by name"""
print(f"Hello, {name}!")
print(greet.__doc__) # This function greets a person by name
help(greet) # Shows help information

Exercise 8: Function Without Docstring

What will be the value of __doc__ for a function that doesn’t have a docstring?

Answer:
def no_docstring():
pass
print(no_docstring.__doc__) # None

Functions without docstrings have __doc__ set to None.

Course Progress

Section 36 of 61

Back to Course