Skip to main content

2.4 - Functions and Modules

Functions and modules are powerful tools in Python that enhance code organization, reusability, and modularity. Functions allow you to encapsulate blocks of code, while modules enable the creation of reusable code files. By mastering these concepts, you can write clean and efficient Python programs.

2.4.1 - Functions

2.4.1.1 - Defining Functions

In Python, functions are defined using the def keyword, followed by the function name and a pair of parentheses. The function body is indented under the function definition.

def greet():
print("Hello, world!")

2.4.1.2 - Function Arguments

Functions can take inputs called arguments or parameters. These are specified within the parentheses when defining the function. Arguments can have default values, which are used if no value is provided when calling the function.

def greet(name):
print(f"Hello, {name}!")

greet("Alice") # Output: Hello, Alice!

2.4.1.3 - Returning Values

Functions can return values using the return statement. The returned value can be stored in a variable or used directly.

def add(a, b):
return a + b

result = add(3, 5)
print(result) # Output: 8

2.4.1.4 - Scope and Variables

Variables defined within a function have local scope, meaning they are only accessible within that function. Variables defined outside of any function have global scope and can be accessed from any part of the program.

global_var = "I am global"

def my_function():
local_var = "I am local"
print(global_var) # Output: I am global
print(local_var) # Output: I am local

my_function()

2.4.1.5 - Pure Functions

Definition of Pure Functions

Pure functions are a fundamental concept in functional programming and an important aspect of writing clean, maintainable code in Python. A function is considered "pure" if it satisfies two main criteria:

  1. Deterministic Output: The output of the function is solely determined by its input values. Given the same input, a pure function always returns the same output.
  2. No Side Effects: The function does not cause any observable side effects in the program. This means it does not modify any external state (like global variables, I/O operations, or data mutations) outside its scope.

Benefits of Pure Functions

  • Predictability: Since the output is only dependent on the input, pure functions are more predictable and easier to debug.
  • Reusability: They can be reused across different parts of a program without concern for hidden states or side effects.
  • Testability: Pure functions are easier to test due to their deterministic nature.
  • Parallelizable: Lack of side effects means they can run in parallel without causing race conditions.

Examples of Pure Functions

# Example 1: A simple pure function
def add(a, b):
return a + b

# This function is pure because it always returns the same output for the same input
# and does not interact with any external state.

# Example 2: Another pure function
def format_date(year, month, day):
return f"{year:04d}-{month:02d}-{day:02d}"

# This function is pure as it only computes a return value based on its inputs
# without altering any external data or state.

Non-Pure Function for Comparison

# An example of a non-pure function
counter = 0 # external state

def increment_counter(amount):
global counter
counter += amount
return counter

# This function is not pure because it modifies the external 'counter' variable.

Incorporating Pure Functions in Your Code

When writing Python code, try to make functions pure whenever possible. This enhances readability and maintainability. However, it's important to note that not all functions can or should be pure, especially when dealing with I/O operations, state management in applications, or interacting with external systems.


2.4.2 - Modules

2.4.2.1 - Creating Modules

A module is a file containing Python code. To create a module, simply save a .py file with the desired code. For example, a module named math_operations.py could contain functions for mathematical operations.

# math_operations.py

def add(a, b):
return a + b

def subtract(a, b):
return a - b

2.4.2.2 - Importing Modules

To use a module in another Python script, it must be imported using the import statement. This allows access to the functions and variables defined in the module.

import math_operations

result = math_operations.add(3, 5)
print(result) # Output: 8

2.4.2.3 - Using Module Functions

After importing a module, its functions can be used by prefixing them with the module name followed by a dot.

import math_operations

result = math_operations.subtract(10, 3)
print(result) # Output: 7

2.4.2.4 - Aliasing Modules

Modules can be imported with an alias using the as keyword. This provides a shorter name to refer to the module in the code.

import math_operations as mo

result = mo.add(2, 2)
print(result) # Output: 4

2.4.2.5 - Creating Custom Modules

Apart from using built-in and third-party modules, you can create your own custom modules by defining functions and variables in a separate Python file.

# custom_module.py

def multiply(a, b):
return a * b

pi = 3.14159
import custom_module

result = custom_module.multiply(4, 5)
print(result) # Output: 20
print(custom_module.pi) # Output: 3.14159

Based on the structure of your guide, this new chapter on "First-Class and Higher-Order Functions" could follow the existing chapter on functions. Let's number it as 2.5, considering it's a continuation of the functions topic. Here's the rewritten chapter with examples and code blocks:


2.4.3 First-Class and Higher-Order Functions

2.4.3.1 - Introduction

What are First-Class Functions?

First-class functions in Python means that functions are treated as “first-class citizens”. This entails:

  • They can be passed as arguments to other functions.
  • They can be returned by another function.
  • They can be assigned to a variable.

What are Higher-Order Functions?

A higher-order function either:

  • Takes one or more functions as arguments.
  • Returns a function as its result.

2.4.3.2 - First-Class Functions in Python

Assigning Functions to Variables

def greet(name):
return f"Hello, {name}!"

greet_someone = greet
print(greet_someone("Alice"))

Output: Hello, Alice!

Functions as Arguments

def greet(name):
return f"Hello, {name}!"

def loud(func, arg):
result = func(arg)
return result.upper()

print(loud(greet, "Bob"))

Output: HELLO, BOB!

Functions Returning Functions

def parent(num):
def first_child():
return "Hi, I am Emma"

def second_child():
return "Call me Liam"

if num == 1:
return first_child
else:
return second_child

first = parent(1)
print(first())

Output: Hi, I am Emma

2.4.3.3 - Higher-Order Functions

Using Built-In Higher-Order Functions

# map()
numbers = [1, 2, 3, 4]
squared = map(lambda x: x ** 2, numbers)
print(list(squared))

Output: [1, 4, 9, 16]

# filter()
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers))

Output: [2, 4, 6]

Creating Custom Higher-Order Functions

def logger(func):
def log_func(*args):
print(f"Running '{func.__name__}' with arguments {args}")
print(f"Result: {func(*args)}")
return log_func

def add(x, y):
return x + y

def sub(x, y):
return x - y

add_logger = logger(add)
sub_logger = logger(sub)

add_logger(3, 3)
sub_logger(10, 5)

Output: Running 'add' with arguments (3, 3)...


2.4.4 - Lambda Functions

Lambda functions in Python, often referred to as anonymous functions, are a powerful feature that allows you to create small, one-off functions without needing the formal structure of regular function definition using def. In Python 3.12, lambda functions continue to be a useful tool for concise code writing. Here's a detailed guide to understanding and using lambda functions in Python 3.12.

2.4.4.1 - Understanding Lambda Functions

Syntax:

lambda arguments: expression
  • lambda is the keyword that signifies the start of the lambda function.
  • arguments are the parameters that you pass to the function. These can be multiple arguments separated by commas.
  • expression is a single expression that the function returns. It starts after the colon :.

2.4.4.2 - Lambda Functions Characteristics

  1. Single Expression: Unlike a regular function, a lambda function can only contain a single expression. There's no need for a return statement; the result of the expression is automatically returned.

  2. Anonymity: Lambda functions are anonymous, which means they don't need a name. However, they can be assigned to a variable.

  3. Inline Usage: They are often used where functions are required for a short period, particularly within other functions like filter(), map(), and sorted().

2.4.4.3 - Examples of Lambda Functions

Basic Example

# A simple lambda function that adds two numbers
add = lambda x, y: x + y
print(add(5, 3)) # Output: 8

Used with map()

# Using lambda with map to square each number in a list
numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x**2, numbers)
print(list(squared)) # Output: [1, 4, 9, 16, 25]

Used with filter()

# Using lambda with filter to find even numbers in a list
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers)) # Output: [2, 4, 6]

Used with sorted()

# Sorting a list of tuples based on the second element
data = [(1, 'd'), (3, 'b'), (4, 'a'), (2, 'c')]
sorted_data = sorted(data, key=lambda x: x[1])
print(sorted_data) # Output: [(4, 'a'), (3, 'b'), (2, 'c'), (1, 'd')]

2.4.4.4 - Best Practices of Lambda Functions

  • Readability: Use lambda functions for simple operations. For complex functions, a regular function is more readable.
  • One-time Use: They are best used in cases where the function is needed temporarily and for a short time.
  • Avoid Overuse: Overusing lambda functions can make your code harder to understand and maintain.

2.4.4.5 - Limitations Of Lambda Functions

  • Single Expression: Lambda functions are limited to a single expression, which limits their complexity.
  • No Statements: You cannot use statements like if, for, etc., within a lambda function. However, you can use conditional expressions.