+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 291 of 365

๐Ÿ“˜ Functools Module: Advanced Tools

Master functools module: advanced tools in Python with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿ’ŽAdvanced
35 min read

Prerequisites

  • Basic understanding of programming concepts ๐Ÿ“
  • Python installation (3.8+) ๐Ÿ
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write clean, Pythonic code โœจ

๐ŸŽฏ Introduction

Welcome to the fascinating world of Pythonโ€™s functools module! ๐ŸŽ‰ In this advanced tutorial, weโ€™ll unlock the power of functional programming tools that can transform your Python code from good to absolutely brilliant.

Have you ever wished you could make your functions smarter, faster, or more flexible? Thatโ€™s exactly what functools does! Whether youโ€™re building high-performance web applications ๐ŸŒ, data processing pipelines ๐Ÿ”„, or elegant libraries ๐Ÿ“š, mastering these advanced tools will elevate your Python game to the next level.

By the end of this tutorial, youโ€™ll be wielding functools like a pro, writing code thatโ€™s not just functional, but beautifully functional! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Functools

๐Ÿค” What is Functools?

Think of functools as a Swiss Army knife ๐Ÿ”ช for functions. Just like how a Swiss Army knife has specialized tools for different tasks, functools provides specialized decorators and functions that enhance your regular Python functions with superpowers!

In Python terms, functools is a module that provides higher-order functions and operations on callable objects. This means you can:

  • โœจ Cache expensive function results automatically
  • ๐Ÿš€ Create specialized versions of functions with pre-filled arguments
  • ๐Ÿ›ก๏ธ Transform functions to handle different signatures
  • ๐ŸŽฏ Build powerful function pipelines and compositions

๐Ÿ’ก Why Use Functools?

Hereโ€™s why Python developers love functools:

  1. Performance Boost ๐Ÿš€: Cache results to avoid redundant computations
  2. Code Elegance ๐Ÿ’Ž: Write cleaner, more expressive functional code
  3. Memory Efficiency ๐Ÿ“Š: Smart caching strategies that respect memory limits
  4. Flexibility ๐ŸŽจ: Adapt functions to different use cases without rewriting

Real-world example: Imagine youโ€™re building a data analysis tool ๐Ÿ“Š that repeatedly calculates Fibonacci numbers. With functools.lru_cache, you can make it 1000x faster with just one line!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ The Power of lru_cache

Letโ€™s start with the most popular tool in the toolkit:

from functools import lru_cache
import time

# ๐ŸŒ Without caching - slow!
def fibonacci_slow(n):
    if n < 2:
        return n
    return fibonacci_slow(n-1) + fibonacci_slow(n-2)

# ๐Ÿš€ With caching - blazing fast!
@lru_cache(maxsize=128)
def fibonacci_fast(n):
    if n < 2:
        return n
    return fibonacci_fast(n-1) + fibonacci_fast(n-2)

# ๐Ÿงช Let's test the difference!
start = time.time()
result_slow = fibonacci_slow(35)
slow_time = time.time() - start

start = time.time()
result_fast = fibonacci_fast(35)
fast_time = time.time() - start

print(f"๐ŸŒ Slow version: {slow_time:.2f} seconds")
print(f"๐Ÿš€ Fast version: {fast_time:.4f} seconds")
print(f"โšก Speed improvement: {slow_time/fast_time:.0f}x faster!")

๐Ÿ’ก Explanation: The @lru_cache decorator remembers previous results, turning our exponential algorithm into a linear one! The โ€œLRUโ€ stands for โ€œLeast Recently Usedโ€ - it keeps the most useful results in memory.

๐ŸŽฏ Partial Functions - Pre-fill Your Arguments

Hereโ€™s a pattern youโ€™ll use constantly:

from functools import partial

# ๐ŸŽจ Original function with multiple parameters
def greet(greeting, name, emoji="๐Ÿ˜Š"):
    return f"{greeting}, {name}! {emoji}"

# ๐Ÿ—๏ธ Create specialized versions
say_hello = partial(greet, "Hello")  # Pre-fill first argument
say_good_morning = partial(greet, "Good morning", emoji="โ˜€๏ธ")

# ๐ŸŽฎ Use them like regular functions!
print(say_hello("Alice"))  # Hello, Alice! ๐Ÿ˜Š
print(say_hello("Bob", emoji="๐ŸŽ‰"))  # Hello, Bob! ๐ŸŽ‰
print(say_good_morning("Charlie"))  # Good morning, Charlie! โ˜€๏ธ

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Smart Shopping Cart with Caching

Letโ€™s build a real e-commerce price calculator:

from functools import lru_cache, partial
from decimal import Decimal
import requests

# ๐Ÿ›๏ธ Product pricing system
class PricingEngine:
    def __init__(self):
        self.base_tax_rate = Decimal("0.08")  # 8% tax
        
    # ๐Ÿ’ฐ Cache expensive API calls
    @lru_cache(maxsize=1000)
    def get_product_price(self, product_id):
        """Fetch product price from API (simulated)"""
        print(f"๐ŸŒ Fetching price for product {product_id}...")
        # Simulate API call
        prices = {
            "laptop": Decimal("999.99"),
            "mouse": Decimal("29.99"),
            "keyboard": Decimal("79.99"),
            "monitor": Decimal("299.99")
        }
        return prices.get(product_id, Decimal("0"))
    
    # ๐ŸŽฏ Cache discount calculations
    @lru_cache(maxsize=500)
    def calculate_discount(self, price, discount_code):
        """Calculate discount based on code"""
        discounts = {
            "SAVE10": Decimal("0.10"),
            "SAVE20": Decimal("0.20"),
            "VIP30": Decimal("0.30")
        }
        discount_rate = discounts.get(discount_code, Decimal("0"))
        return price * discount_rate
    
    # ๐Ÿ“Š Create specialized tax calculators
    def create_tax_calculator(self, state):
        """Create state-specific tax calculator"""
        state_rates = {
            "CA": Decimal("0.0725"),
            "TX": Decimal("0.0625"),
            "NY": Decimal("0.08")
        }
        rate = state_rates.get(state, self.base_tax_rate)
        return partial(self._calculate_tax, rate)
    
    def _calculate_tax(self, rate, amount):
        return amount * rate

# ๐Ÿ›’ Shopping cart implementation
class ShoppingCart:
    def __init__(self, pricing_engine, state="CA"):
        self.pricing_engine = pricing_engine
        self.items = []
        self.calculate_tax = pricing_engine.create_tax_calculator(state)
        
    def add_item(self, product_id, quantity=1):
        self.items.append({"id": product_id, "qty": quantity})
        print(f"โœ… Added {quantity}x {product_id} to cart!")
        
    def get_total(self, discount_code=None):
        subtotal = Decimal("0")
        
        # ๐Ÿ’ฐ Calculate subtotal (with caching!)
        for item in self.items:
            price = self.pricing_engine.get_product_price(item["id"])
            subtotal += price * item["qty"]
        
        # ๐ŸŽ Apply discount if provided
        discount = Decimal("0")
        if discount_code:
            discount = self.pricing_engine.calculate_discount(
                subtotal, discount_code
            )
            
        # ๐Ÿ“Š Calculate tax
        taxable_amount = subtotal - discount
        tax = self.calculate_tax(taxable_amount)
        
        total = taxable_amount + tax
        
        print(f"\n๐Ÿงพ Receipt:")
        print(f"  ๐Ÿ“ฆ Subtotal: ${subtotal:.2f}")
        if discount > 0:
            print(f"  ๐ŸŽ Discount: -${discount:.2f}")
        print(f"  ๐Ÿ’ธ Tax: ${tax:.2f}")
        print(f"  ๐Ÿ’ณ Total: ${total:.2f}")
        
        return total

# ๐ŸŽฎ Let's shop!
engine = PricingEngine()
cart = ShoppingCart(engine, state="CA")

cart.add_item("laptop")
cart.add_item("mouse", 2)
cart.add_item("keyboard")

# First calculation - hits the "API"
total1 = cart.get_total("SAVE20")

# Second calculation - uses cache! ๐Ÿš€
print("\n๐Ÿ”„ Calculating again (watch - no API calls!):")
total2 = cart.get_total("SAVE20")

# Check cache stats
print(f"\n๐Ÿ“Š Cache stats: {engine.get_product_price.cache_info()}")

๐ŸŽฏ Try it yourself: Add a method to clear specific items from the cache when prices update!

๐ŸŽฎ Example 2: Game Achievement System with Advanced Decorators

Letโ€™s create a sophisticated achievement tracking system:

from functools import wraps, partial, reduce, singledispatch
from datetime import datetime
import operator

# ๐Ÿ† Achievement decorator factory
def achievement(name, points, emoji="๐Ÿ†"):
    """Decorator that awards achievements for completing actions"""
    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            result = func(self, *args, **kwargs)
            
            # Award achievement if not already earned
            if name not in self.achievements:
                self.achievements[name] = {
                    "points": points,
                    "emoji": emoji,
                    "earned_at": datetime.now()
                }
                print(f"\n๐ŸŽ‰ Achievement Unlocked: {emoji} {name} (+{points} points)")
            
            return result
        return wrapper
    return decorator

# ๐ŸŽฎ Game system with achievements
class GameEngine:
    def __init__(self, player_name):
        self.player_name = player_name
        self.score = 0
        self.level = 1
        self.achievements = {}
        self.combo_multiplier = 1
        print(f"๐ŸŽฎ Welcome, {player_name}! Let's play!")
    
    # ๐ŸŽฏ Use partial to create difficulty-specific scorers
    def _add_score(self, base_points, difficulty_multiplier):
        points = base_points * difficulty_multiplier * self.combo_multiplier
        self.score += points
        print(f"โœจ +{points} points! (Total: {self.score})")
        return points
    
    @property
    def score_easy(self):
        return partial(self._add_score, difficulty_multiplier=1)
    
    @property
    def score_medium(self):
        return partial(self._add_score, difficulty_multiplier=1.5)
    
    @property
    def score_hard(self):
        return partial(self._add_score, difficulty_multiplier=2)
    
    # ๐Ÿ† Achievement-decorated methods
    @achievement("First Blood", 100, "๐Ÿ—ก๏ธ")
    def defeat_enemy(self, enemy_type):
        print(f"โš”๏ธ Defeated a {enemy_type}!")
        self.score_medium(50)
    
    @achievement("Speedrunner", 500, "โšก")
    def complete_level_fast(self, time_seconds):
        if time_seconds < 60:
            print(f"๐Ÿƒ Level completed in {time_seconds}s!")
            self.score_hard(200)
            return True
        return False
    
    @achievement("Combo Master", 300, "๐Ÿ”ฅ")
    def build_combo(self, hits):
        if hits >= 10:
            self.combo_multiplier = 2
            print(f"๐Ÿ”ฅ {hits}-hit combo! Multiplier active!")
            return True
        return False
    
    # ๐ŸŽจ Single dispatch for handling different power-ups
    @singledispatch
    def use_powerup(self, powerup):
        print(f"โ“ Unknown powerup: {powerup}")
    
    @use_powerup.register(str)
    def _(self, powerup):
        powerups = {
            "health": ("โค๏ธ", 100),
            "shield": ("๐Ÿ›ก๏ธ", 150),
            "boost": ("๐Ÿš€", 200)
        }
        if powerup in powerups:
            emoji, points = powerups[powerup]
            print(f"{emoji} Used {powerup} powerup!")
            self.score_easy(points)
    
    @use_powerup.register(list)
    def _(self, powerup_combo):
        print(f"๐Ÿ’ซ Combo powerup: {' + '.join(powerup_combo)}!")
        total = reduce(operator.add, [100] * len(powerup_combo))
        self.score_hard(total)
    
    # ๐Ÿ“Š Stats with reduce
    def get_total_achievement_points(self):
        if not self.achievements:
            return 0
        return reduce(
            operator.add,
            (ach["points"] for ach in self.achievements.values())
        )
    
    def show_stats(self):
        print(f"\n๐Ÿ“Š {self.player_name}'s Stats:")
        print(f"  ๐ŸŽฏ Score: {self.score}")
        print(f"  ๐Ÿ† Achievements: {len(self.achievements)}")
        print(f"  โญ Achievement Points: {self.get_total_achievement_points()}")
        
        if self.achievements:
            print("\n๐Ÿ† Earned Achievements:")
            for name, data in self.achievements.items():
                print(f"  {data['emoji']} {name} - {data['points']} points")

# ๐ŸŽฎ Play the game!
game = GameEngine("Player One")

# Earn some achievements
game.defeat_enemy("goblin")
game.defeat_enemy("orc")  # No duplicate achievement!
game.complete_level_fast(45)
game.build_combo(15)

# Use different powerup types
game.use_powerup("health")
game.use_powerup(["health", "shield", "boost"])

# Show final stats
game.show_stats()

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Custom Caching with cache

Python 3.9+ introduced @cache - an even simpler caching decorator:

from functools import cache, cached_property
import time

# ๐ŸŽฏ Infinite cache (no size limit)
@cache
def heavy_computation(x, y):
    print(f"๐Ÿ”ง Computing {x} * {y}...")
    time.sleep(1)  # Simulate heavy work
    return x * y

# ๐Ÿ—๏ธ Cached properties for classes
class DataProcessor:
    def __init__(self, data):
        self.data = data
        self._processed = False
    
    @cached_property
    def analysis_result(self):
        """This computation runs only once per instance!"""
        print("๐Ÿ”ฌ Analyzing data... (this message appears only once)")
        time.sleep(2)  # Simulate complex analysis
        return {
            "mean": sum(self.data) / len(self.data),
            "max": max(self.data),
            "min": min(self.data),
            "sparkles": "โœจ" * len(self.data)
        }

# ๐Ÿงช Test it out
result1 = heavy_computation(7, 8)  # Takes 1 second
result2 = heavy_computation(7, 8)  # Instant! ๐Ÿš€

processor = DataProcessor([1, 2, 3, 4, 5])
print(processor.analysis_result)  # Takes 2 seconds
print(processor.analysis_result)  # Instant access! โšก

๐Ÿ—๏ธ Advanced Topic 2: Function Composition with reduce

For the functional programming enthusiasts:

from functools import reduce, partial
from operator import add, mul

# ๐Ÿ”„ Function pipeline creator
def compose(*functions):
    """Compose functions right-to-left (mathematical order)"""
    def inner(arg):
        return reduce(lambda result, f: f(result), reversed(functions), arg)
    return inner

# ๐ŸŽจ Create a data transformation pipeline
def add_emoji(text):
    return f"{text} ๐ŸŽจ"

def make_uppercase(text):
    return text.upper()

def add_excitement(text):
    return f"{text}!!!"

def wrap_in_stars(text):
    return f"โญ {text} โญ"

# ๐Ÿ”ง Compose them together!
super_transform = compose(
    wrap_in_stars,
    add_excitement,
    make_uppercase,
    add_emoji
)

# ๐ŸŽฎ Use the pipeline
result = super_transform("hello python")
print(result)  # โญ HELLO PYTHON ๐ŸŽจ!!! โญ

# ๐Ÿš€ Advanced: Parameterized pipeline
def multiply_by(n):
    return partial(mul, n)

def add_to(n):
    return partial(add, n)

# Create a mathematical pipeline
math_pipeline = compose(
    multiply_by(2),    # Finally, multiply by 2
    add_to(10),        # Then add 10
    multiply_by(3),    # First, multiply by 3
)

print(f"5 โ†’ {math_pipeline(5)}")  # 5 โ†’ 3*5 + 10 โ†’ 25*2 โ†’ 50

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Mutable Default Arguments in lru_cache

# โŒ Wrong - mutable defaults cause cache issues!
@lru_cache
def process_data(items=[]):
    items.append("processed")
    return len(items)

# This will give unexpected results!
print(process_data())  # 1
print(process_data())  # 1 (cached, but list was mutated!)

# โœ… Correct - use immutable defaults
@lru_cache
def process_data(items=None):
    if items is None:
        items = []
    items = items + ["processed"]  # Create new list
    return len(items)

# Or use tuples for immutable sequences
@lru_cache
def process_data_better(items=()):
    return len(items) + 1

๐Ÿคฏ Pitfall 2: Cache Size Explosion

# โŒ Dangerous - unlimited cache can eat all memory!
@lru_cache(maxsize=None)  # Unlimited cache
def generate_report(user_id, date, options):
    # If you have millions of combinations, RIP memory! ๐Ÿ’ฅ
    return f"Report for {user_id} on {date}"

# โœ… Safe - set reasonable limits
@lru_cache(maxsize=1000)  # Limited cache
def generate_report(user_id, date, options):
    return f"Report for {user_id} on {date}"

# ๐Ÿ›ก๏ธ Even better - cache only expensive parts
@lru_cache(maxsize=100)
def fetch_user_data(user_id):
    # Cache only the expensive database call
    return expensive_db_query(user_id)

def generate_report(user_id, date, options):
    user_data = fetch_user_data(user_id)  # Cached!
    return format_report(user_data, date, options)  # Not cached

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Cache Wisely: Only cache pure functions (same input โ†’ same output)
  2. ๐Ÿ“ Size Matters: Set appropriate maxsize based on memory constraints
  3. ๐Ÿงน Clear When Needed: Use cache_clear() to reset cache manually
  4. ๐Ÿ“Š Monitor Usage: Check cache_info() to optimize cache size
  5. ๐Ÿš€ Profile First: Measure before optimizing - not everything needs caching!

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Smart Recipe Recommendation System

Create a recipe recommendation engine with caching and function composition:

๐Ÿ“‹ Requirements:

  • โœ… Cache ingredient prices from an โ€œAPIโ€
  • ๐Ÿท๏ธ Support dietary restrictions (vegan, gluten-free, etc.)
  • ๐Ÿ‘ค Remember user preferences
  • ๐Ÿ“Š Calculate nutrition scores with caching
  • ๐ŸŽจ Each recipe needs an emoji!

๐Ÿš€ Bonus Points:

  • Implement partial functions for cuisine-specific recipes
  • Use singledispatch for different recommendation strategies
  • Add cache statistics reporting

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
from functools import lru_cache, partial, singledispatch, reduce
from typing import List, Dict, Set
import random
from datetime import datetime

# ๐Ÿณ Recipe recommendation system
class RecipeRecommender:
    def __init__(self):
        self.user_preferences = {}
        self.recommendation_count = 0
        
    # ๐Ÿ’ฐ Cache ingredient prices
    @lru_cache(maxsize=200)
    def get_ingredient_price(self, ingredient: str) -> float:
        """Simulate API call for ingredient prices"""
        print(f"๐Ÿ’ต Fetching price for {ingredient}...")
        prices = {
            "tomato": 0.50, "pasta": 1.00, "cheese": 2.50,
            "lettuce": 1.50, "chicken": 5.00, "tofu": 3.00,
            "rice": 0.80, "beans": 1.20, "avocado": 2.00
        }
        return prices.get(ingredient, 1.00)
    
    # ๐Ÿ“Š Cache nutrition calculations
    @lru_cache(maxsize=100)
    def calculate_nutrition_score(self, *ingredients) -> int:
        """Calculate nutrition score based on ingredients"""
        print(f"๐Ÿ”ฌ Calculating nutrition for {len(ingredients)} ingredients...")
        scores = {
            "tomato": 9, "lettuce": 8, "chicken": 7,
            "tofu": 8, "rice": 6, "beans": 9,
            "avocado": 9, "pasta": 5, "cheese": 6
        }
        total = sum(scores.get(ing, 5) for ing in ingredients)
        return min(total, 10)  # Cap at 10
    
    # ๐ŸŽฏ Partial functions for cuisine types
    def _create_recipe(self, cuisine, name, ingredients, emoji):
        return {
            "name": name,
            "cuisine": cuisine,
            "ingredients": ingredients,
            "emoji": emoji,
            "nutrition": self.calculate_nutrition_score(*tuple(ingredients)),
            "cost": sum(self.get_ingredient_price(ing) for ing in ingredients)
        }
    
    @property
    def italian_recipe(self):
        return partial(self._create_recipe, "Italian")
    
    @property
    def asian_recipe(self):
        return partial(self._create_recipe, "Asian")
    
    @property
    def mexican_recipe(self):
        return partial(self._create_recipe, "Mexican")
    
    # ๐Ÿ” Single dispatch for recommendation strategies
    @singledispatch
    def recommend(self, criteria):
        """Base recommendation method"""
        return self._get_all_recipes()
    
    @recommend.register(str)
    def _(self, dietary_restriction: str):
        """Recommend based on dietary restriction"""
        all_recipes = self._get_all_recipes()
        
        filters = {
            "vegan": lambda r: "chicken" not in r["ingredients"],
            "gluten-free": lambda r: "pasta" not in r["ingredients"],
            "low-cost": lambda r: r["cost"] < 7.00
        }
        
        filter_func = filters.get(dietary_restriction, lambda r: True)
        filtered = list(filter(filter_func, all_recipes))
        
        print(f"๐ŸŽฏ Found {len(filtered)} {dietary_restriction} recipes!")
        return filtered
    
    @recommend.register(list)
    def _(self, preferred_ingredients: List[str]):
        """Recommend based on ingredient preferences"""
        all_recipes = self._get_all_recipes()
        
        # Score recipes by matching ingredients
        scored_recipes = []
        for recipe in all_recipes:
            matches = len(set(preferred_ingredients) & set(recipe["ingredients"]))
            if matches > 0:
                scored_recipes.append((matches, recipe))
        
        # Sort by match count
        scored_recipes.sort(key=lambda x: x[0], reverse=True)
        
        print(f"๐ŸŽฏ Found {len(scored_recipes)} recipes with your ingredients!")
        return [recipe for _, recipe in scored_recipes]
    
    def _get_all_recipes(self):
        """Get all available recipes"""
        recipes = [
            self.italian_recipe("Margherita Pizza", ["tomato", "cheese"], "๐Ÿ•"),
            self.italian_recipe("Pasta Primavera", ["pasta", "tomato"], "๐Ÿ"),
            self.asian_recipe("Veggie Stir Fry", ["rice", "tofu"], "๐Ÿฅ˜"),
            self.asian_recipe("Chicken Rice Bowl", ["rice", "chicken"], "๐Ÿš"),
            self.mexican_recipe("Bean Tacos", ["beans", "lettuce"], "๐ŸŒฎ"),
            self.mexican_recipe("Guacamole Bowl", ["avocado", "tomato"], "๐Ÿฅ‘"),
        ]
        return recipes
    
    # ๐Ÿ“Š Cache statistics
    def show_cache_stats(self):
        print("\n๐Ÿ“Š Cache Performance Stats:")
        price_info = self.get_ingredient_price.cache_info()
        nutrition_info = self.calculate_nutrition_score.cache_info()
        
        print(f"๐Ÿ’ต Price Cache: {price_info}")
        print(f"๐Ÿ”ฌ Nutrition Cache: {nutrition_info}")
        
        # Calculate hit rate
        if price_info.hits + price_info.misses > 0:
            hit_rate = price_info.hits / (price_info.hits + price_info.misses) * 100
            print(f"โœจ Cache Hit Rate: {hit_rate:.1f}%")
    
    # ๐ŸŽจ Beautiful recipe display
    def display_recipes(self, recipes: List[Dict], limit=3):
        print(f"\n๐Ÿฝ๏ธ Top {min(limit, len(recipes))} Recommendations:")
        
        for i, recipe in enumerate(recipes[:limit], 1):
            print(f"\n{i}. {recipe['emoji']} {recipe['name']}")
            print(f"   ๐ŸŒ Cuisine: {recipe['cuisine']}")
            print(f"   ๐Ÿฅ— Ingredients: {', '.join(recipe['ingredients'])}")
            print(f"   ๐Ÿ’ช Nutrition Score: {'โญ' * recipe['nutrition']}")
            print(f"   ๐Ÿ’ฐ Cost: ${recipe['cost']:.2f}")

# ๐ŸŽฎ Test the system!
recommender = RecipeRecommender()

# Test different recommendation strategies
print("๐ŸŒฑ Vegan Recommendations:")
vegan_recipes = recommender.recommend("vegan")
recommender.display_recipes(vegan_recipes)

print("\n\n๐Ÿฅ‘ Recipes with preferred ingredients:")
preferred_recipes = recommender.recommend(["avocado", "tomato"])
recommender.display_recipes(preferred_recipes)

print("\n\n๐Ÿ’ธ Budget-friendly options:")
budget_recipes = recommender.recommend("low-cost")
recommender.display_recipes(budget_recipes)

# Check cache performance
recommender.show_cache_stats()

# ๐Ÿ”„ Make more requests to see cache in action
print("\n\n๐Ÿ”„ Making duplicate requests (watch cache hits!):")
_ = recommender.recommend("vegan")
_ = recommender.recommend(["rice", "tofu"])
recommender.show_cache_stats()

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered advanced functools techniques! Hereโ€™s what you can now do:

  • โœ… Optimize performance with intelligent caching strategies ๐Ÿ’ช
  • โœ… Create flexible functions using partial application ๐Ÿ›ก๏ธ
  • โœ… Build function pipelines with composition patterns ๐ŸŽฏ
  • โœ… Handle different types elegantly with singledispatch ๐Ÿ›
  • โœ… Write beautiful functional Python thatโ€™s fast and maintainable! ๐Ÿš€

Remember: functools isnโ€™t just about making code faster - itโ€™s about making it more elegant, more reusable, and more Pythonic! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve unlocked the power of functools!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the recipe recommender exercise
  2. ๐Ÿ—๏ธ Refactor existing code to use caching where appropriate
  3. ๐Ÿ“š Explore functools.wraps for better decorators
  4. ๐ŸŒŸ Move on to our next tutorial on advanced itertools techniques!

Remember: Every Python expert started exactly where you are now. Keep experimenting, keep learning, and most importantly, have fun with functional programming! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ