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
defto define andreturnto produce a result. - Use
lambdafor short anonymous functions (single expression only). - Use type hints (
x: int) to document expected types. - Use docstrings to describe the function's contract.
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 optionalreturn. 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) ornonlocal(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, usedef. - Decorators: A decorator is a function that takes a function and returns a new one — used for logging, caching, authentication. Applied with
@decoratorabove the function. - Type hints: Optional annotations (
x: int -> bool) improve readability, enable static analysis (mypy), and integrate beautifully with IDEs.
Code Examples
def final_bill(price, qty, tax=0.18):
subtotal = price * qty
return subtotal + subtotal * tax
print(round(final_bill(99.5, 3), 2))
def fact(n):
return 1 if n <= 1 else n * fact(n - 1)
print(fact(6))
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print([fib(i) for i in range(8)])
people = [("Riya", 22), ("Sam", 19), ("Anu", 31)]
print(sorted(people, key=lambda p: p[1]))
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"))
Hi Ankit
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
c = make_counter()
print(c(), c(), c())
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_cacheto memoise expensive recursive functions. - Add doctests for tiny functions: examples right inside the docstring.
Common Mistakes
- Calling without parentheses:
my_funcreferences 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
returncan 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 andsum(). - Problem 5: Build a decorator
@timerthat 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
defandlambda? - 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