Use @functools.lru_cache decorator or implement custom caching with dict. Stores function results, returns cached value for same arguments. Consider cache size, argument hashability. Example: @lru_cache(maxsize=None). Good for expensive computations.
Currying transforms function with multiple arguments into series of single-argument functions. Implement using nested functions or partial application. Example: def curry(f): return lambda x: lambda y: f(x,y). Used for function composition and partial application.
Return self from methods to enable chaining: obj.method1().method2(). Common in builder pattern and fluent interfaces. Consider readability and maintenance. Example: query builders, object configuration.
Decorators are higher-order functions that modify other functions. Use @decorator syntax or function_name = decorator(function_name). Common uses: logging, timing, authentication, caching. Implement using nested functions or classes with __call__. Can take arguments using decorator factories.
map(func, iterable) applies function to each element. filter(func, iterable) keeps elements where function returns True. reduce(func, iterable) accumulates values using binary function. All return iterators (except reduce). Consider list comprehensions or generator expressions as alternatives.
Use functools.partial to create new function with fixed arguments. Reduces function arity by pre-filling arguments. Example: from functools import partial; new_func = partial(original_func, arg1). Useful for callback functions and function factories.
Generator expressions create iterator objects: (expression for item in iterable). Like list comprehensions but more memory efficient. Use when iterating once over large sequences. Syntax uses parentheses: sum(x*2 for x in range(1000)).
Recursion is function calling itself. Limited by recursion depth (default 1000). Use tail recursion optimization or iteration for deep recursion. Example: factorial implementation. Consider stack overflow risk and performance implications.
Pure functions always return same output for same input, have no side effects. Benefits: easier testing, debugging, parallelization. Example: def add(x,y): return x+y. Avoid global state, I/O operations. Key concept in functional programming.
Python doesn't support traditional overloading. Use default arguments, *args, **kwargs, or @singledispatch decorator. Alternative: multiple dispatch based on argument types. Consider interface clarity vs flexibility.
Implement __iter__ and __next__ methods. __iter__ returns iterator object, __next__ provides next value or raises StopIteration. Used in for loops, generators. Example: custom range implementation. Consider memory efficiency.
functools provides tools for functional programming: reduce, partial, lru_cache, wraps, singledispatch. Enhances function manipulation and optimization. Example: @wraps preserves function metadata in decorators.
Tail recursion occurs when recursive call is last operation. Python doesn't optimize tail recursion automatically. Convert to iteration or use trampolining for optimization. Consider stack overflow prevention.
Lambda functions are anonymous, single-expression functions using lambda keyword. Syntax: lambda args: expression. Best for simple operations, function arguments, and when function is used once. Example: lambda x: x*2. Limited to single expression, no statements allowed.
Generators are functions using yield keyword to return values incrementally. Create iterator objects, maintain state between calls. Memory efficient for large sequences. Used with for loops, next(). Example: def gen(): yield from range(10). Don't store all values in memory.
Python uses LEGB scope (Local, Enclosing, Global, Built-in). Closures capture variable values from enclosing scope. Use nonlocal for enclosing scope, global for global scope. Closures common in decorators and factory functions.
Annotations provide type hints: def func(x: int) -> str. Stored in __annotations__ dict. Not enforced at runtime, used by type checkers, IDEs, documentation. PEP 484 defines type hinting standards. Help with code understanding and verification.
Positional arguments (standard), keyword arguments (name=value), default arguments (def func(x=1)), variable arguments (*args for tuple, **kwargs for dict). Order matters: positional, *args, keyword, **kwargs. Consider argument flexibility vs clarity.
Dict comprehension: {key:value for item in iterable}. Set comprehension: {expression for item in iterable}. More concise than loops, creates new dict/set. Example: {x:x**2 for x in range(5)}. Consider readability vs complexity.
Nested functions have access to outer function variables (closure). Use nonlocal keyword to modify enclosed variables. Common in decorators and callbacks. Consider readability and maintenance implications.
Use Either/Maybe patterns, return tuple of (success, result), or raise exceptions. Handle errors explicitly in function composition. Consider monadic error handling. Maintain functional purity where possible.
List comprehensions create lists using compact syntax: [expression for item in iterable if condition]. More readable and often faster than loops. Creates new list in memory. Example: [x**2 for x in range(10) if x % 2 == 0]. Good for transforming and filtering data.
Higher-order functions take functions as arguments or return functions. Examples: map, filter, decorators. Enable function composition and abstraction. Consider type hints for function arguments. Common in functional programming patterns.
Use docstrings (triple quotes) for function documentation. Include description, parameters, return value, examples. Access via help() or __doc__. Follow PEP 257 conventions. Consider tools like Sphinx for documentation generation.
Coroutines use async/await syntax, support concurrent programming. Unlike generators, can receive values with send(). Used with asyncio for asynchronous operations. Example: async def coro(): await operation(). Consider event loop integration.
Function composition combines functions: f(g(x)). Implement using higher-order functions or operator module. Example: compose = lambda f, g: lambda x: f(g(x)). Consider readability and error handling.
Functions are objects with attributes. Access/set via function.__dict__. Common attributes: __name__, __doc__, __module__. Use @wraps to preserve metadata in decorators. Consider documentation and debugging implications.
Function factories create and return new functions. Use closures to capture configuration. Common in parameterized decorators, specialized functions. Example: def make_multiplier(n): return lambda x: x * n.