Build Functions in Python - With Funtools, Decorators and Lambda

Chetan TekamChetan Tekam
6 min read

Script demonstrates decorators and lambda functions by implementing a simple task:

  • timing function execution and

  • processing a list of numbers with lambda-based operations (e.g., square, cube).

Objective: Understand Decorators and Lambda


What New Concept

Decorators:

  • What: Functions that modify other functions (e.g., timing_decorator adds timing to process_numbers).

  • Why: Adds functionality (e.g., logging, timing) without changing the original function.

  • Where: Used in frameworks (e.g., Flask for routing) or DevOps scripts (e.g., logging automation tasks).

  • New: Uses @decorator syntax and wraps to preserve function metadata.

  • Exceptions: Incorrect decorator implementation can alter function behavior (e.g., missing @wraps loses metadata).


Lambda Functions:

  • What: Anonymous functions defined inline (e.g., lambda x: x ** 2).

  • Why: Concise for simple operations passed as arguments.

  • Where: Common in functional programming (e.g., map(), filter()) or callbacks.

  • New: Defined with lambda args: expression; here, used for squaring/cubing numbers.

  • Exceptions: Limited to single expressions; complex logic needs regular functions.


Callable Type:

  • What: Type hint Callable[[float], float] for functions taking a float and returning a float.

  • Why: Specifies function signatures in type hints for clarity.

  • Where: Used in professional code with dynamic function passing.

  • New: Requires typing.Callable (one of few typing imports needed in Python 3.12).

  • Exceptions: Incorrect type usage caught by mypy.


Function Metadata (@wraps):

  • What: Preserves original function’s name and docstring in decorators.

  • Why: Ensures decorated functions remain introspectable (e.g., for debugging).

  • Where: Standard in decorator implementation.

  • New: Imported from functools.

  • Exceptions: Omitting @wraps can cause confusion in debugging.


Project summary

  • Process a list of numbers and return valid input with operations and time consumed, if provide empty raise an ValueError and if list contain other data type than numbers then they consider invalid input.

  • Uses @decorator syntax and wraps to preserve function metadata.

  • Imported from functools.

  • Defined with lambda args: expression; here, used for squaring/cubing numbers.

  • Requires typing.Callable (one of few typing imports needed in Python 3.12).


Structure

core-python
├── basics
│   └── functions
│       ├── README.md
│       └── main.py
└── tests
    └── test_functions.py
  • main.py functions script.

  • test_functions.py test script for email validator.


How I Built it

  1. Build a pseudo flow with pen and paper.

  2. Start writing code and also my experience on how I end up with issues:

    • Let’s start with main():

      • Provide input to process_numbers().

      • numbers assign to a list of float values.

  • Let’s understand lambda arg: expression

    • square assign to lambda function to do the operation x**2 on every x argument.

    • cube assign to lambda function to do the operation x**3 on every x argument.

  • Here the numbers list and square/cube lambda operations use as a parameter of process_numbers(), whose output store in squared_numbers/cube_numbers which later get print.

  • Here the empty list and square lambda operation use as a parameter of process_numbers(), whose output store in empty_result which later get print.

    I need to do with try … except because when if empty list provide to process_number() it raise ValueError but after that program just stop, so to counter that I make one here main(). Here the value of ValueError in process_numbers() passed to this except and print e.

  • Here I provide invalid input, which means list contain other data type than numbers.

    However, when invalid send to process_numbers() the invalid one like ”abc” taken out and only valid input process with operation i.e square.

    • This are the built-in modules that needed:

      • time use for

      • typing use for type test, here it use to import

        • Callable use if variable assign to a function (e.g lambda )

        • Any use for considering variable assign to any data type.

      • functools is a tools for working with functions and callable objects.

        • wraps Preserves original function’s name and docstring in decorators.

To know more about modules, libraries and keyword:

  • Open Terminal

      pydoc3.x <library_name/module_name/keyword>
      # eg. pydoc3.10 time
      # eg. pydoc3.10 print
    
  • Next process_numbers()

    • timing_decorator adds timing to process_numbers

    • Argument:

      • numbers take input list.

      • operation take lambda variable (e.g. square/cube)

    • Return format: → list[float] means list of float data type.

    • Return: list of valid input which went to operation.

This above is the refactor and recently updated one. I need to fixed it few times because I unnecessary put type conversion at the time calling operation() so I get an TypeError error which later can be handle with error handling. Anyway here is the previous way I to handle error:

    try:
        if num + "":
    #    >>> 2 + ""
    #    TypeError: unsupported operand type(s) for +: 'int' and 'str'
            invalid_results.append(num)
    except TypeError:    # Here TypeError be handle
        results.append(operation(num))

    ## Other way
    result = results.append(operation(num))
    invalid = invalid_results.append(num)
    # using `type() .. is not`
    if type(num) is not str:
        result
    else:
       invalid

    # using `type() .. in`
    result if type(num) in (int, float) else invalid

    # using isinstance()
    if isinstance(num, str):
        invalid_results.append(num)
    else:
        results.append(operation(num))
  • Finally timing_decorator adds timing to process_numbers.


How to run

  1. Change directory to core-python, so that test can also be performed.

     # optional: in python-foundation dir
     # source venv/bin/activate    # Windows: venv\Scripts\activate
     cd core-python
    
  2. Run main.py.

     python3 basics/functions/main.py
    

    Output:

      Processing numbers with square operation:
      process_numbers took 0.0000 seconds
      Squared: [1.0, 4.0, 9.0, 16.0, 25.0]
    
      Processing numbers with cube operation:
      process_numbers took 0.0000 seconds
      Cubed: [1.0, 8.0, 27.0, 64.0, 125.0]
    
      Processing empty list:
      Error - Input list cannot be empty
    
      Processing invalid input:
      Skipped invalid inputs: ['abc']
      process_numbers took 0.0000 seconds
      Invalid input result: [1.0, 9.0]
    
  3. Run test_functions.py:

     PYTHONPATH=. pytest tests/test_functions.py -v
    

    Output:

      $ PYTHONPATH=. pytest tests/test_functions.py -vv
      ========================== test session starts ===========================
      platform linux -- Python 3.12.8, pytest-8.4.0, pluggy-1.6.0 -- /PATH/TO/PYTHON-FOUNDATION/.venv/bin/python3.12
      cachedir: .pytest_cache
      rootdir: /PATH/TO/PYTHON-FOUNDATION/core-python
      plugins: anyio-4.9.0
      collected 4 items
    
      tests/test_functions.py::test_process_numbers_valid_input PASSED   [ 25%]
      tests/test_functions.py::test_process_numbers_invalid_input PASSED [ 50%]
      tests/test_functions.py::test_process_numbers_empty_list PASSED    [ 75%]
      tests/test_functions.py::test_process_numbers_invalid_operation PASSED [100%]
    
      =========================== 4 passed in 0.01s ============================
    

Exceptions/Edge Cases

  • Decorator Issues: Missing @wraps could obscure function metadata (handled with functools.wraps).

  • Invalid Inputs: Empty lists or non-numeric inputs (handled with ValueError, TypeError).

  • Type Hints: Use mypy for static checking:

      pip install mypy
      mypy core-python/basics/functions.py
    
  • Performance: Timing decorator may show negligible times for fast functions.


Next

Control Structure explore control structures with Python 3.12’s structural pattern matching (match statement) and introduce sets for unique data handling.


Github: Repo - Python Foundation/functions

Click here for: Project list


🤝 How to Contribute / Follow Along

  • Clone the repo or copy scripts to try on your own machine.

  • Share your output or improvements in the comments.

  • DM me on GitHub or Hashnode for feedback!


📢 Let’s Connect

Follow me on Hashnode to get notified when I publish new Python projects 👇

➡️ Chetan Tekam


0
Subscribe to my newsletter

Read articles from Chetan Tekam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Chetan Tekam
Chetan Tekam