Advanced Functions

Daniel Sarney
Python Intermediate

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:

  1. Lambda Practice: Use lambda functions to sort a list of dictionaries by multiple keys (e.g., sort people by age, then by name).

  2. Map and Filter: Create a pipeline that filters numbers divisible by 3, squares them, and sums the results using map, filter, and reduce.

  3. Flexible Function: Write a function that accepts any number of keyword arguments and returns a formatted string with all key-value pairs.

  4. Type Hints: Add type hints to a function that processes a list of user dictionaries and returns statistics.

  5. 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.

Video Tutorial Coming Soon

The video tutorial for this lesson will be available soon. Check back later!

Continue Learning

12 lessons
Python For Beginners

Start your learning journey with Python For Beginners. This course includes comprehensive lessons covering everything you need to know.