User-Defined Functions In Python — PBA Institute Tutorial
Chapter 08 · Python Programming Series
12 min read Beginner

Designing Your Own User-Defined Functions

Built-in functions like print() and len() can only take you so far. User-defined functions let you express your own logic — business rules, math formulas, parsers, validators — and reuse them everywhere. This chapter focuses on designing functions that are clean, robust, and a pleasure to read.

Overview

🛠️

Custom Logic

Encapsulate any rule or algorithm specific to your problem domain.

📚

Self-Documenting

Docstrings + type hints describe how to use the function correctly.

🔁

Recursive Power

Express elegant algorithms — factorial, fibonacci, tree walks — naturally.

🎭

Decoratable

Wrap functions with cross-cutting concerns like caching or logging.

🪶

Lightweight Lambdas

Throw-away one-liners for sort keys and filters.

Syntax

  • Use def to define and return to produce a result.
  • Use lambda for short anonymous functions (single expression only).
  • Use type hints (x: int) to document expected types.
  • Use docstrings to describe the function's contract.
User-Defined Functions — Syntax
def total_with_tax(amount: float, rate: float = 0.18) -> float:
    """Return amount + GST."""
    return amount + amount * rate

# Lambda equivalent (for trivial cases)
total_with_tax_lambda = lambda a, r=0.18: a + a * r

Detailed Explanation

  • Anatomy of a function: A UDF has a header (def name(params):), an optional docstring, a body, and an optional return. Indentation defines the body.
  • Local vs global scope: Variables inside a function are local by default. Outer variables can be read, but to assign to them you need global (module level) or nonlocal (enclosing function).
  • Recursion: A function may call itself. Always include a base case. Useful for tree traversal, factorials, divide-and-conquer algorithms — but slower than iteration in CPython.
  • Lambdas: Anonymous, one-expression functions. Useful as short callbacks for sorted, map, filter. For anything multi-line, use def.
  • Decorators: A decorator is a function that takes a function and returns a new one — used for logging, caching, authentication. Applied with @decorator above the function.
  • Type hints: Optional annotations (x: int -> bool) improve readability, enable static analysis (mypy), and integrate beautifully with IDEs.

Code Examples

Example 1 — Compute Final Bill
def final_bill(price, qty, tax=0.18):
    subtotal = price * qty
    return subtotal + subtotal * tax

print(round(final_bill(99.5, 3), 2))
Output 352.23
Example 2 — Recursive Factorial
def fact(n):
    return 1 if n <= 1 else n * fact(n - 1)

print(fact(6))
Output 720
Example 3 — Recursive Fibonacci
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print([fib(i) for i in range(8)])
Output [0, 1, 1, 2, 3, 5, 8, 13]
Example 4 — Lambda with sorted()
people = [("Riya", 22), ("Sam", 19), ("Anu", 31)]
print(sorted(people, key=lambda p: p[1]))
Output [('Sam', 19), ('Riya', 22), ('Anu', 31)]
Example 5 — Simple Decorator
def log(fn):
    def wrapper(*a, **kw):
        print("calling", fn.__name__)
        return fn(*a, **kw)
    return wrapper

@log
def hello(name):
    return "Hi " + name

print(hello("Ankit"))
Output calling hello
Hi Ankit
Example 6 — Closure (Counter Factory)
def make_counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc

c = make_counter()
print(c(), c(), c())
Output 1 2 3

Real-World Use Cases

Billing Engines

Tax/discount rules wrapped as small, testable UDFs.

Validators

Email, phone, GST, PAN validation as composable functions.

Schedulers

Cron-style scripts call user functions on a schedule.

Analytics

Custom aggregators applied on pandas dataframes.

Web Scraping

Site-specific parsers as targeted UDFs.

Hardware Drivers

Wrap low-level register reads/writes behind clean function names.

Notes & Pro Tips

  • Aim for one job per function — single-responsibility principle.
  • Return early to flatten conditionals: if not ok: return None.
  • Prefer pure functions where possible — easier to reason about and test.
  • Avoid global state — pass dependencies explicitly.
  • Use functools.lru_cache to memoise expensive recursive functions.
  • Add doctests for tiny functions: examples right inside the docstring.

Common Mistakes

  • Calling without parentheses: my_func references the object; my_func() calls it.
  • Returning inside a loop too early: may miss processing the rest of the data.
  • Recursion depth exceeded: Python's default limit is 1000; use iteration for deep cases.
  • Closure variable surprise: Loop variables captured by reference, not value. Use defaults lambda x=x:.
  • Mutable default arguments: def f(x=[]): — the list is shared across calls.
  • Silent return None: forgetting return can confuse callers expecting a value.

Practice Problems

  • Problem 1: Write is_prime(n) that returns True/False.
  • Problem 2: Build celsius_to_fahrenheit(c) and the reverse.
  • Problem 3: Create a recursive function to compute the sum of digits of a number.
  • Problem 4: Write count_vowels(s) using a lambda and sum().
  • Problem 5: Build a decorator @timer that prints how long the wrapped function takes.
  • Problem 6: Implement a closure-based counter that supports increment and reset operations.

Interview Questions

  • Q1. What is the difference between built-in and user-defined functions?
  • Q2. Explain recursion. What are its risks and limits in Python?
  • Q3. What is a closure? When would you use one?
  • Q4. How do decorators work under the hood?
  • Q5. What is the difference between def and lambda?
  • Q6. Why are mutable default arguments dangerous?

Frequently Asked Questions

  • Q1: How is a UDF different from a built-in?
    Built-ins are pre-shipped with Python (print, len). UDFs are written by you using def or lambda.
  • Q2: Can a function call itself?
    Yes — that's recursion. Always include a base case to terminate.
  • Q3: When should I use a lambda?
    For very short, one-expression callbacks (sort keys, filter predicates). Otherwise prefer def.
  • Q4: What is a decorator?
    A function that takes another function and returns a new one — used for logging, caching, auth checks, etc.
  • Q5: What is a closure?
    A function that remembers variables from the scope where it was created, even after that scope has finished.
  • Q6: Are type hints enforced at runtime?
    No. They are documentation/static-analysis hints. Tools like mypy check them; Python itself ignores them.

Summary

User-defined functions are how you turn ideas into reusable, well-named tools. With recursion, lambdas, closures, decorators, and type hints in your toolbox, you can express algorithms elegantly and build maintainable systems. Always design for one job, document the contract, and prefer pure logic over hidden side-effects.

Continue Learning

Previous

Go to Previous Chapter

Next

Go to Next Chapter