# Python Functions Cheatsheet ## Basic Function Types ### Built-in Functions ```python # Common built-in functions len(sequence) # Returns length of a sequence print(*objects) # Prints objects to console type(object) # Returns the type of an object range(start, stop) # Creates sequence of numbers max(iterable) # Returns maximum value min(iterable) # Returns minimum value sum(iterable) # Sums items in an iterable ``` ### User-defined Functions ```python # Basic function definition def function_name(parameters): """Docstring: explains what function does""" # Function body return value # Optional return statement # Example def square(x): """Returns the square of a number.""" return x * x ``` ### Lambda Functions (Anonymous) ```python # lambda arguments: expression square = lambda x: x * x add = lambda x, y: x + y # With higher-order functions list(map(lambda x: x * 2, [1, 2, 3])) # [2, 4, 6] ``` ## Function Parameters ### No Parameters ```python def say_hello(): return "Hello, world!" # Call with no arguments say_hello() ``` ### Required Parameters ```python def multiply(a, b): return a * b # Must provide all required arguments result = multiply(5, 3) # 15 ``` ### Default Parameters ```python def power(base, exponent=2): return base ** exponent # Default parameters are optional power(5) # 25 (uses default exponent=2) power(5, 3) # 125 (overrides default) ``` ### Keyword Arguments ```python def create_person(name, age, job): return f"{name} is {age} years old and works as a {job}" # Order doesn't matter with keyword args create_person(age=30, job="developer", name="Alice") ``` ### Positional-Only and Keyword-Only Arguments (Python 3.8+) ```python def process_data(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): """ pos1, pos2: positional-only (before /) pos_or_kwd: positional or keyword kwd1, kwd2: keyword-only (after *) """ return f"Processing: {pos1}, {pos2}, {pos_or_kwd}, {kwd1}, {kwd2}" # Valid calls: process_data(1, 2, 3, kwd1=4, kwd2=5) # All specified process_data(1, 2, pos_or_kwd=3, kwd1=4, kwd2=5) # pos_or_kwd as keyword # Invalid calls: # process_data(pos1=1, pos2=2, pos_or_kwd=3, kwd1=4, kwd2=5) # Error: pos1, pos2 can't be keyword # process_data(1, 2, 3, 4, 5) # Error: kwd1, kwd2 can't be positional ``` ### Variable-length Arguments (*args and **kwargs) ```python def sum_all(*args): """Accept any number of positional arguments""" return sum(args) def user_info(**kwargs): """Accept any number of keyword arguments""" for key, value in kwargs.items(): print(f"{key}: {value}") # Usage sum_all(1, 2, 3, 4, 5) # 15 user_info(name="Alice", age=30, city="New York") # Combination def process_all(*args, **kwargs): print(f"Args: {args}") print(f"Kwargs: {kwargs}") process_all(1, 2, 3, name="Alice", age=30) # Args: (1, 2, 3), Kwargs: {'name': 'Alice', 'age': 30} ``` ## Variable Scope and the LEGB Rule ```python # The LEGB rule determines how Python looks up variable names: # Local → Enclosing → Global → Built-in x = 10 # Global scope def outer_function(): y = 20 # Enclosing scope for inner_function def inner_function(): z = 30 # Local scope print(f"Local: {z}") print(f"Enclosing: {y}") print(f"Global: {x}") print(f"Built-in: {len}") # Built-in function return inner_function func = outer_function() func() # Prints all variables from different scopes # Modifying variables in outer scopes def modify_global(): global x x = 100 # Modifies the global x def modify_enclosing(): y = 20 def inner(): nonlocal y y = 200 # Modifies the enclosing y inner() print(y) # 200 modify_global() print(x) # 100 modify_enclosing() ``` ## Advanced Function Concepts ### Recursive Functions ```python def factorial(n): if n <= 1: return 1 return n * factorial(n-1) ``` ### Closures ```python def counter_factory(): count = 0 # This variable is "enclosed" in the returned function def increment(): nonlocal count count += 1 return count return increment counter = counter_factory() print(counter()) # 1 print(counter()) # 2 print(counter()) # 3 # Another example: function with "memory" def make_multiplier(factor): def multiply(number): return number * factor # Uses factor from enclosing scope return multiply double = make_multiplier(2) triple = make_multiplier(3) print(double(5)) # 10 print(triple(5)) # 15 ``` ### Higher-order Functions ```python # Functions that take/return other functions def apply(func, value): return func(value) # Usage apply(lambda x: x*2, 5) # 10 ``` ### Generator Functions ```python def count_up_to(n): i = 1 while i <= n: yield i i += 1 # Creates a generator object counter = count_up_to(3) next(counter) # 1 next(counter) # 2 next(counter) # 3 ``` ### Decorators ```python from functools import wraps def my_decorator(func): @wraps(func) # Preserves metadata of the original function def wrapper(*args, **kwargs): print("Before function call") result = func(*args, **kwargs) print("After function call") return result return wrapper @my_decorator def greet(name): """Greet someone by name.""" return f"Hello, {name}!" # The metadata is preserved print(greet.__name__) # 'greet' (not 'wrapper') print(greet.__doc__) # 'Greet someone by name.' ``` ### Type Hints (PEP 484) ```python from typing import List, Dict, Tuple, Optional, Union, Any, Callable def headline(text: str, align: bool = True) -> str: """Format text as a headline.""" if align: return f"{text.title()}\n{'-' * len(text)}" else: return f"{text.title()}" # More complex type hints def process_items(items: List[str]) -> Dict[str, int]: return {item: len(item) for item in items} # Optional parameters def greeting(name: Optional[str] = None) -> str: if name is None: return "Hello, Guest!" return f"Hello, {name}!" # Union types def process_id(id: Union[int, str]) -> str: return str(id) # Function type hints def apply_formatter(value: str, formatter: Callable[[str], str]) -> str: return formatter(value) ``` ## Docstring Conventions (PEP 257) ```python def calculate_discount(price: float, rate: float = 0.1) -> float: """ Calculate the discounted price. Args: price: The original price rate: The discount rate (default: 0.1) Returns: The price after applying the discount Raises: ValueError: If price or rate is negative """ if price < 0 or rate < 0: raise ValueError("Price and rate must be non-negative") return price * (1 - rate) # Google style def fetch_data(resource_id: str, max_results: int = 100) -> List[Dict]: """Fetches data from the specified resource. Args: resource_id: The ID of the resource to fetch. max_results: The maximum number of results to return. Returns: A list of dictionaries containing the fetched data. Raises: ConnectionError: If unable to connect to the data source. """ pass ``` ## Class Methods ### Instance Methods ```python class MyClass: def instance_method(self, x): """Access instance attributes with self""" self.value = x ``` ### Class Methods ```python class MyClass: @classmethod def class_method(cls, x): """Access class attributes with cls""" return cls(x) # Can create instances ``` ### Static Methods ```python class MyClass: @staticmethod def static_method(x): """No access to instance or class""" return x * 2 ``` ## Functional Programming ### map() ```python # Apply function to all items in an iterable numbers = [1, 2, 3, 4] squares = list(map(lambda x: x**2, numbers)) # [1, 4, 9, 16] ``` ### filter() ```python # Filter items based on a function numbers = [1, 2, 3, 4, 5, 6] evens = list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4, 6] ``` ### reduce() ```python from functools import reduce # Apply function cumulatively to items numbers = [1, 2, 3, 4] product = reduce(lambda x, y: x * y, numbers) # 24 ``` ### Other functools Utilities ```python from functools import partial, lru_cache # partial: Create new functions with pre-filled arguments def power(base, exponent): return base ** exponent square = partial(power, exponent=2) cube = partial(power, exponent=3) print(square(4)) # 16 print(cube(4)) # 64 # lru_cache: Memoization decorator (caches results) @lru_cache(maxsize=128) def fibonacci(n): if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2) # First call computes result, subsequent calls with same args use the cache print(fibonacci(30)) # Fast, even for large values ``` ## Function Design Best Practices 1. **Single Responsibility**: Functions should do one thing well 2. **Pure Functions**: Avoid side effects when possible 3. **Descriptive Names**: Use clear, descriptive function names 4. **Parameter Count**: Aim for fewer than 4 parameters 5. **Default Values**: Use sensible defaults for optional parameters 6. **Docstrings**: Document what the function does, parameters, and return values 7. **Type Hints**: Use type annotations for better tooling and clarity 8. **Error Handling**: Validate inputs and handle errors gracefully 9. **Return Values**: Be consistent with return types 10. **Early Returns**: Use early returns to avoid deep nesting