# Build Functions in Python - With Funtools, Decorators and Lambda

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

```bash
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.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753944019548/236a4eb9-e58c-4074-8f9a-40ea22d272e1.png align="center")
    
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.
            
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753951190680/acbc199b-49ac-42dd-a22a-a5bcce1deaec.png align="center")
    
    * 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.
            
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753951198759/676c17dc-c11a-4b21-8a6b-56e40c5cb025.png align="center")
    
    * 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.
        
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753951211960/05140059-3aec-42f7-ac05-16045f90cf15.png align="center")
    
    * 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`.
        
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753951218665/5b2522eb-9798-4993-950f-93f6dd2fcf9f.png align="center")
    
    * 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`.
        
3. ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753951233268/4b2f7c81-8976-4c1c-8011-5527899f812b.png align="center")
    
    * 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.
                
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753951279651/7e0dd11d-7931-4071-95c9-ee01cae0aa0e.png align="center")
    
    To know more about modules, libraries and keyword:
    
    * Open Terminal
        
        ```bash
        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.
            
            ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753954581417/8bf02a9e-2664-4744-90f8-b8a5605ad239.png align="center")
            
    
    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:
    
    ```python
    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`.
        
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753951296681/c667d011-0688-492b-bbdb-98b7fdadb5b2.png align="center")
    

---

## How to run

1. Change directory to `core-python`, so that test can also be performed.
    
    ```sh
    # optional: in python-foundation dir
    # source venv/bin/activate    # Windows: venv\Scripts\activate
    cd core-python
    ```
    
2. Run `main.py`.
    
    ```sh
    python3 basics/functions/main.py
    ```
    
    **Output**:
    
    ```sh
     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`:
    
    ```sh
    PYTHONPATH=. pytest tests/test_functions.py -v
    ```
    
    **Output**:
    
    ```sh
     $ 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:
    
    ```bash
    pip install mypy
    mypy core-python/basics/functions.py
    ```
    
* **Performance**: Timing decorator may show negligible times for fast functions.
    

---

## Next

[**Control Structure**](https://python-foundation.hashnode.dev/building-controlstructure-in-python-with-switch-and-set-concept) explore control structures with Python 3.12’s structural pattern matching (match statement) and introduce sets for unique data handling.

---

## Links

Github: Repo - [Python Foundation/functions](https://github.com/Chetan3500/python-foundation/tree/main/core-python/basics/functions)

[Click here for: Project list](https://github.com/Chetan3500/python-foundation/tree/main/core-python/basics/email_validator)

---

## 🤝 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](@Chetan3500)

---
