You've learned the basics of functions, but Python offers much more. Advanced function features let you write more flexible, concise, and powerful code. In this lesson, you'll discover lambda functions for quick, one-line functions; higher-order functions that work with other functions; and flexible argument handling that makes your functions adaptable.
These concepts are essential for functional programming in Python and appear frequently in real-world code. By mastering them, you'll write more Pythonic code and understand how many Python libraries work under the hood.
What You'll Learn
- Lambda functions and when to use them
- Higher-order functions (map, filter, reduce)
- Function annotations and type hints
- Variable-length arguments (args, *kwargs)
- Keyword-only and position-only arguments
- Best practices for advanced function features
Lambda Functions
Lambda functions are anonymous functions defined with the lambda keyword. They're perfect for short, simple operations:
# Regular function
def square(x):
return x ** 2
# Lambda function (equivalent)
square = lambda x: x ** 2
print(square(5)) # 25
# Lambda with multiple arguments
add = lambda a, b: a + b
print(add(3, 4)) # 7
Lambdas are most useful when you need a simple function temporarily, especially with higher-order functions:
# Sorting with a custom key
people = [
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 30},
{"name": "Charlie", "age": 20}
]
# Sort by age using lambda
sorted_by_age = sorted(people, key=lambda p: p["age"])
print(sorted_by_age)
# [{'name': 'Charlie', 'age': 20}, {'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}]
# Filter with lambda
adults = list(filter(lambda p: p["age"] >= 21, people))
print(adults) # [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}]
Higher-Order Functions
Higher-order functions are functions that take other functions as arguments or return functions. Python's built-in map(), filter(), and reduce() are common examples:
The map() Function
map() applies a function to every item in an iterable:
# Using map with a regular function
def double(x):
return x * 2
numbers = [1, 2, 3, 4, 5]
doubled = list(map(double, numbers))
print(doubled) # [2, 4, 6, 8, 10]
# Using map with lambda
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# Map with multiple iterables
a = [1, 2, 3]
b = [4, 5, 6]
sums = list(map(lambda x, y: x + y, a, b))
print(sums) # [5, 7, 9]
The filter() Function
filter() returns items from an iterable where a condition is true:
# Filter even numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]
# Filter words longer than 4 characters
words = ["python", "is", "awesome", "and", "powerful"]
long_words = list(filter(lambda w: len(w) > 4, words))
print(long_words) # ['python', 'awesome', 'powerful']
The reduce() Function
reduce() (from functools) applies a function cumulatively to items:
from functools import reduce
# Sum all numbers
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, numbers)
print(total) # 15
# Find maximum
maximum = reduce(lambda x, y: x if x > y else y, numbers)
print(maximum) # 5
# Multiply all numbers
product = reduce(lambda x, y: x * y, numbers)
print(product) # 120
Variable-Length Arguments
Python allows functions to accept variable numbers of arguments using *args and **kwargs:
*args for Positional Arguments
*args collects extra positional arguments into a tuple:
def sum_all(*args):
"""Sum all provided arguments."""
total = 0
for num in args:
total += num
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
print(sum_all()) # 0
# More concise version
def sum_all(*args):
return sum(args)
**kwargs for Keyword Arguments
**kwargs collects extra keyword arguments into a dictionary:
def print_info(**kwargs):
"""Print all provided keyword arguments."""
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="London")
# name: Alice
# age: 25
# city: London
# Combining regular args, *args, and **kwargs
def flexible_function(required, *args, **kwargs):
print(f"Required: {required}")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
flexible_function("hello", 1, 2, 3, name="Alice", age=25)
# Required: hello
# Args: (1, 2, 3)
# Kwargs: {'name': 'Alice', 'age': 25}
Keyword-Only and Position-Only Arguments
Python 3 allows you to specify which arguments must be positional or keyword-only:
# Keyword-only arguments (after *)
def greet(name, *, greeting="Hello", punctuation="!"):
print(f"{greeting}, {name}{punctuation}")
greet("Alice") # Hello, Alice!
greet("Bob", greeting="Hi") # Hi, Bob!
# greet("Charlie", "Hey") # Error! greeting must be keyword-only
# Position-only arguments (before /)
def divide(x, y, /):
"""x and y must be positional."""
return x / y
result = divide(10, 2) # 5.0
# result = divide(x=10, y=2) # Error! x and y must be positional
# Combining both
def example(pos1, pos2, /, normal, *, keyword_only):
pass
Function Annotations and Type Hints
Type hints make your code more readable and help with documentation:
# Basic type hints
def add(a: int, b: int) -> int:
return a + b
# Type hints with default values
def greet(name: str, age: int = 0) -> str:
return f"{name} is {age} years old"
# Type hints for lists and dictionaries
from typing import List, Dict, Optional
def process_numbers(numbers: List[int]) -> int:
return sum(numbers)
def get_user_info(user_id: int) -> Dict[str, str]:
return {"name": "Alice", "email": "alice@example.com"}
def find_user(name: str) -> Optional[Dict[str, str]]:
# Returns Dict or None
if name == "Alice":
return {"name": "Alice"}
return None
Practical Examples
Here are real-world examples combining these concepts:
# Example 1: Flexible configuration function
def create_config(*args, **kwargs):
"""Create configuration with defaults and overrides."""
default_config = {
"host": "localhost",
"port": 8080,
"debug": False
}
# Update with provided kwargs
default_config.update(kwargs)
return default_config
config = create_config(host="example.com", port=3000)
print(config) # {'host': 'example.com', 'port': 3000, 'debug': False}
# Example 2: Data processing pipeline
from functools import reduce
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Filter, transform, and reduce
result = reduce(
lambda x, y: x + y,
map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numbers))
)
print(result) # 220 (sum of squares of even numbers)
# Example 3: Flexible logger
def log_message(message: str, *tags, level: str = "INFO", **metadata):
"""Log message with tags and metadata."""
tag_str = ", ".join(tags) if tags else "none"
meta_str = ", ".join(f"{k}={v}" for k, v in metadata.items())
print(f"[{level}] {message} | Tags: {tag_str} | {meta_str}")
log_message("User logged in", "auth", "user", level="INFO", user_id=123, ip="192.168.1.1")
Try It Yourself
Practice these exercises:
-
Lambda Practice: Use lambda functions to sort a list of dictionaries by multiple keys (e.g., sort people by age, then by name).
-
Map and Filter: Create a pipeline that filters numbers divisible by 3, squares them, and sums the results using map, filter, and reduce.
-
Flexible Function: Write a function that accepts any number of keyword arguments and returns a formatted string with all key-value pairs.
-
Type Hints: Add type hints to a function that processes a list of user dictionaries and returns statistics.
-
Higher-Order Function: Create a function that takes another function and a list, applies the function to each element, and returns the results.
Summary
You've learned powerful function features that make Python code more flexible and expressive. Lambda functions provide concise one-line functions. Higher-order functions like map(), filter(), and reduce() enable functional programming patterns. Variable-length arguments (*args and **kwargs) make functions adaptable. Type hints improve code documentation and IDE support.
These features appear throughout Python's standard library and popular packages. Understanding them will help you read and write more Pythonic code. While you don't need to use them everywhere, knowing when they're appropriate is a mark of an intermediate Python programmer.
What's Next?
In the next lesson, we'll explore decorators—one of Python's most powerful features. Decorators let you modify or extend functions without changing their code. You'll learn how to create your own decorators and use them for timing, logging, validation, and more.