Python Programming Labs

Master advanced Python concepts: decorators, generators, and modular programming.

Advanced Python Patterns - Module 5

Learn powerful Python features used by professional developers.

Lab 13: Python Decorators
Expert
Coding Challenge
Your Task: Learn to create and use decorators - functions that modify the behavior of other functions. Decorators are a powerful pattern used extensively in frameworks like Flask and Django.

Detailed Requirements:
1. Understand what a decorator is:
# A decorator is a function that takes a function # and returns a modified version of that function # Without decorator syntax: def my_decorator(func): def wrapper(): print("Before function call") func() print("After function call") return wrapper def say_hello(): print("Hello!") say_hello = my_decorator(say_hello) # Manual decoration

2. Use the @ decorator syntax:
# The @ symbol is syntactic sugar for decoration @my_decorator def say_hello(): print("Hello!") # This is equivalent to: say_hello = my_decorator(say_hello)

3. Create a timing decorator:
import time def timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} took {end-start:.4f} seconds") return result return wrapper @timer def slow_function(): time.sleep(1) print("Done!")    • *args, **kwargs allows the wrapper to accept any arguments
   • Always return the result of the original function

4. Create a decorator with arguments:
def repeat(times): def decorator(func): def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def greet(name): print(f"Hello, {name}!")

💡 Pro Tips:
• Use @functools.wraps(func) to preserve function metadata
• Decorators can be stacked: @decorator1 @decorator2
• Common uses: logging, timing, authentication, caching
• Class methods need self passed through *args

Expected Output:
Before function call Hello, World! After function call greet took 0.0001 seconds Hello, Alice! Hello, Alice! Hello, Alice!

Requirements Checklist

Define a decorator function
Create an inner wrapper function
Use the @decorator syntax
Handle *args and **kwargs
Return the wrapper function
Call the decorated function
Output
# Click "Run Code" to execute your Python code # Your program output will appear here
Hints & Tips
• Decorator: def decorator(func): def wrapper(): ... return wrapper
• Apply decorator: @decorator above function definition
• Accept any args: def wrapper(*args, **kwargs):
• Call original: result = func(*args, **kwargs)
• Always return wrapper from decorator
• Return result: return result from wrapper
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below

Lab 14: Python Generators
Expert
Coding Challenge
Your Task: Master generators - memory-efficient iterators that generate values on-the-fly using the yield keyword instead of return.

Detailed Requirements:
1. Understand generators vs lists:
# List: stores ALL values in memory at once numbers_list = [x ** 2 for x in range(1000000)] # Uses lots of memory! # Generator: generates values ONE AT A TIME numbers_gen = (x ** 2 for x in range(1000000)) # Uses minimal memory!

2. Create a generator function with yield:
def count_up_to(n): """Generate numbers from 1 to n""" i = 1 while i <= n: yield i # Pause here, return i, resume on next call i += 1 # Usage: for num in count_up_to(5): print(num) # Prints 1, 2, 3, 4, 5    • yield pauses the function and returns a value
   • Next iteration resumes after the yield
   • Function state is preserved between yields

3. Create a Fibonacci generator:
def fibonacci(limit): """Generate Fibonacci numbers up to limit""" a, b = 0, 1 while a < limit: yield a a, b = b, a + b # Usage: for fib in fibonacci(100): print(fib) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89

4. Use next() to manually iterate:
gen = count_up_to(3) print(next(gen)) # 1 print(next(gen)) # 2 print(next(gen)) # 3 # print(next(gen)) # StopIteration error!

5. Generator expressions:
# Use parentheses instead of brackets squares_gen = (x**2 for x in range(10)) # Convert to list if needed squares_list = list(squares_gen)

💡 Pro Tips:
• Generators are perfect for large datasets or infinite sequences
• Use yield from to delegate to another generator
• Generators can only be iterated once
send() can pass values into a generator

Expected Output:
1 2 3 4 5 Fibonacci: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 First 5 squares: [0, 1, 4, 9, 16]

Requirements Checklist

Create a generator function using yield
Use a while loop in the generator
Iterate over the generator with for loop
Create a generator expression (x for x in ...)
Use next() on a generator
Print values from the generator
Output
# Click "Run Code" to execute your Python code
Hints & Tips
• Generator function: def gen(): yield value
• Generator expression: (x for x in range(10))
• Iterate: for item in generator:
• Manual: next(generator)
• To list: list(generator)
• yield pauses function, returns value, resumes next call
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below

Lab 15: Modules & Packages
Expert
Coding Challenge
Your Task: Learn to organize code using modules and packages, import standard library modules, and understand Python's import system.

Detailed Requirements:
1. Import entire modules:
import math import random import datetime # Use with module prefix print(math.pi) # 3.141592653589793 print(math.sqrt(16)) # 4.0 print(random.randint(1, 10)) # Random number 1-10

2. Import specific functions:
from math import pi, sqrt, ceil, floor from random import choice, shuffle # Use directly without prefix print(pi) # 3.141592653589793 print(sqrt(25)) # 5.0 print(choice(['a', 'b', 'c'])) # Random element

3. Import with aliases:
import datetime as dt from collections import Counter as C now = dt.datetime.now() print(now.strftime("%Y-%m-%d %H:%M")) word_counts = C("hello world".split()) print(word_counts) # Counter({'hello': 1, 'world': 1})

4. Explore useful standard library modules:
# os - Operating system interface import os print(os.getcwd()) # Current directory # json - JSON encoding/decoding import json data = {"name": "Alice", "age": 30} json_str = json.dumps(data) print(json_str) # '{"name": "Alice", "age": 30}' # collections - Specialized containers from collections import defaultdict, namedtuple Point = namedtuple('Point', ['x', 'y']) p = Point(3, 4) print(p.x, p.y) # 3 4

5. Check what's in a module:
import math print(dir(math)) # List all attributes help(math.sqrt) # Get help on a function

💡 Pro Tips:
• Use if __name__ == "__main__": for script entry points
• Avoid from module import * (pollutes namespace)
• Virtual environments isolate project dependencies
• Use pip install package_name for third-party packages

Expected Output:
Pi: 3.141592653589793 Square root of 16: 4.0 Random number: 7 Current time: 2024-01-15 10:30:45 JSON: {"name": "Alice", "age": 30}

Requirements Checklist

Import an entire module (import math)
Import specific functions (from ... import ...)
Use an import alias (import ... as ...)
Use math module functions (sqrt, pi, etc.)
Use random or datetime module
Print results from imported functions
Output
# Click "Run Code" to execute your Python code
Hints & Tips
• Full import: import math then math.sqrt()
• Specific: from math import sqrt, pi
• Alias: import datetime as dt
• Common modules: math, random, datetime, json, os
• Explore: dir(module) lists contents
• Help: help(math.sqrt) shows documentation
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below