Functions

MEDIUM3 min readby AdminJune 20, 2026History
0.0|0 ratingsLog in to rate

Functions are reusable, self-contained blocks of code that execute a specific action. Python supports: * Positional & Keyword arguments (`*args`, `**kwargs`). *...

#ai-engineering#course#functions

Definition

Functions are reusable, self-contained blocks of code that execute a specific action. Python supports:

  • Positional & Keyword arguments (*args, **kwargs).
  • First-class functions (assignable to variables, passable as parameters).
  • Decorators (higher-order functions that modify behavior).
  • Anonymous functions (lambda).

Why It Exists

Modular programming requires clear API boundaries. In agentic frameworks, we use functions to dynamically register tools for LLMs, handle API response formatting, and apply global middleware patterns (such as logging or performance auditing) across multiple modules.

How It Works

Decorators operate using closures:

text
1
2
3
[Call decorated_function()] ──> [Outer Decorator Function] ──> [Pre-execution code (e.g. logging)]
                                                                       │
[Returns Final Output] ◄────── [Post-execution code] ◄────── [Execute Inner core_function()]
  1. Namespace Resolution: Python resolves variables using the LEGB rule (Local, Enclosing, Global, Built-in).
  2. Stack Frame: Each function call pushes a new execution stack frame carrying local variables.
  3. Closure: An inner function remembers the variables defined in its outer enclosing function even after the outer function has finished executing.

Real-World Example

Example

In LlamaIndex or LangChain, decorators are used extensively to register custom python functions as tools that can be dynamically parsed into JSON schema formats for LLM Function Calling.

Python Example

Example

Here is a production-grade decorator that handles retrying LLM API requests with exponential backoff on exceptions:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import time
import functools
from typing import Callable, Any

def retry_api_call(max_retries: int = 3, initial_delay: float = 1.0) -> Callable:
    """Decorator to retry API calls on exceptions with exponential backoff."""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            delay = initial_delay
            for attempt in range(1, max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_retries:
                        raise e
                    print(f"Attempt {attempt} failed: {e}. Retrying in {delay}s...")
                    time.sleep(delay)
                    delay *= 2
            return None
        return wrapper
    return decorator

# Mock flaky API call
@retry_api_call(max_retries=3, initial_delay=0.5)
def call_llm_endpoint(prompt: str) -> str:
    # Simulating a rate limit error on first runs
    call_llm_endpoint.counter = getattr(call_llm_endpoint, "counter", 0) + 1
    if call_llm_endpoint.counter < 3:
        raise ConnectionError("Rate limit exceeded (429).")
    return f"Success! Output for: {prompt}"

if __name__ == "__main__":
    print(call_llm_endpoint("Translate 'Hi' to Spanish"))

Interview Questions

  • Beginner: What is the purpose of *args and **kwargs?
  • Intermediate: What are Python decorators and how do they work under the hood?
  • Advanced: How does functools.wraps prevent losing function metadata when applying decorators?

Interview Answers

  • Beginner: *args allows a function to accept any number of positional arguments as a tuple, while **kwargs allows it to accept any number of keyword arguments as a dictionary.
  • Intermediate: Decorators are wrappers that modify the behavior of a target function without modifying its source code. They take a function as an input, define an inner wrapper function that executes custom logic before and after the target call, and return this inner function.
  • Advanced: When a function is wrapped, its metadata (such as __name__ and __doc__) is overwritten by the decorator wrapper's metadata. @functools.wraps(func) copies the original function's attributes back onto the wrapper, preserving docstrings and identity for debugging and stack traces.

Common Mistakes

  • Mutable default arguments: Writing def add_message(msg, history=[]). The list is created once at function definition, meaning all calls to the function will share the same history instance. Use history=None and initialize inside the function instead.

Assignment

Create a decorator called log_token_usage that calculates the length of input parameters and outputs, estimating the token cost at $0.002 per 1000 characters.


Discussion

Join the discussion! Sign in to leave comments and ask questions.

Loading discussion...