DEV Community

Cover image for Python by Structure: Return Value Transformations with Decorators
Aaron Rose
Aaron Rose

Posted on

Python by Structure: Return Value Transformations with Decorators

Timothy was working on a text processing module when he noticed something odd. "Margaret, I have five functions that all return strings, and they all need to uppercase their results. I'm calling .upper() at the end of every single function. There has to be a better way."

Margaret looked at his code. "You're right - that's repetitive. When you find yourself adding the same transformation to multiple function outputs, that's a perfect use case for a decorator."

"A decorator can transform return values?" Timothy asked. "I thought decorators were just for logging and timing."

The Problem: Repetitive Output Transformations

Margaret showed him what he was doing:

def get_greeting(name):
    return f"hello, {name}".upper()

def get_status():
    return "system online".upper()

def get_error_message(code):
    return f"error code: {code}".upper()
Enter fullscreen mode Exit fullscreen mode

"See the pattern?" Margaret asked. "Every function has .upper() at the end. That's implementation leaking into your business logic."

Timothy nodded. "And if I decide I want title case instead, I have to change it in every function."

"Exactly. Now watch how a decorator solves this."

def uppercase_result(func):
    """Decorator to convert a function's string result to uppercase."""
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        if isinstance(result, str):
            return result.upper()
        return result
    return wrapper

@uppercase_result
def get_greeting(name):
    """Returns a personalized greeting."""
    return f"hello, {name}"

@uppercase_result
def get_status():
    """Returns the system status."""
    return "system online"

@uppercase_result
def get_error_message(code):
    """Returns an error message."""
    return f"error code: {code}"

# Usage
print(get_greeting("Alice"))
print(get_status())
print(get_error_message(404))
Enter fullscreen mode Exit fullscreen mode

Understanding Return Value Interception

Timothy studied the decorator. "So the wrapper calls my original function, gets the result, and then transforms it before returning?"

Margaret showed him the structure:

Tree View:

uppercase_result(func)
    "Decorator to convert a function's string result to uppercase."
    wrapper()
        result = func(*args, **kwargs)
        If isinstance(result, str)
        └── Return result.upper()
        Return result
    Return wrapper

@uppercase_result
get_greeting(name)
    'Returns a personalized greeting.'
    Return f'hello, {name}'

@uppercase_result
get_status()
    'Returns the system status.'
    Return 'system online'

@uppercase_result
get_error_message(code)
    'Returns an error message.'
    Return f'error code: {code}'

print(get_greeting('Alice'))
print(get_status())
print(get_error_message(404))
Enter fullscreen mode Exit fullscreen mode

English View:

Function uppercase_result(func):
  Evaluate "Decorator to convert a function's string result to uppercase.".
  Function wrapper():
    Set result to func(*args, **kwargs).
    If isinstance(result, str):
      Return result.upper().
    Return result.
  Return wrapper.

Decorator @uppercase_result
Function get_greeting(name):
  Evaluate 'Returns a personalized greeting.'.
  Return f'hello, {name}'.

Decorator @uppercase_result
Function get_status():
  Evaluate 'Returns the system status.'.
  Return 'system online'.

Decorator @uppercase_result
Function get_error_message(code):
  Evaluate 'Returns an error message.'.
  Return f'error code: {code}'.

Evaluate print(get_greeting('Alice')).
Evaluate print(get_status()).
Evaluate print(get_error_message(404)).
Enter fullscreen mode Exit fullscreen mode

"Look at the flow," Margaret said. "The wrapper calls func(*args, **kwargs), stores the result, checks if it's a string, and only then applies .upper(). If the result isn't a string, it passes through unchanged."

Timothy traced the execution:

HELLO, ALICE
SYSTEM ONLINE
ERROR CODE: 404
Enter fullscreen mode Exit fullscreen mode

"So the decorator intercepts the return value, transforms it, and hands back the transformed version? My functions just return their normal values, and the decorator handles the uppercasing?"

"Exactly. Your functions stay clean and focused on their logic. The transformation is applied consistently by the decorator."

Type-Safe Transformations

Timothy noticed something in the decorator. "You check isinstance(result, str) before calling .upper(). Why?"

"Because not all functions return strings," Margaret said. "Watch what happens if we decorate a function that returns a number:"

@uppercase_result
def get_error_code():
    """Returns a number, which should not be affected."""
    return 404

print(get_error_code())  # Outputs: 404
Enter fullscreen mode Exit fullscreen mode

"The decorator checks the type and only applies the transformation if appropriate. Non-string results pass through unchanged."

Timothy nodded. "So the decorator is defensive - it transforms what it can and leaves everything else alone."

"Right. That's a good pattern for transformation decorators. Don't assume you know what the function returns; check first."

When to Use Return Value Decorators

"So when would I use this pattern?" Timothy asked.

Margaret listed the use cases:

"Use return value transformation decorators when you need to:

  • Apply consistent formatting to function outputs (uppercase, lowercase, title case)
  • Round or format numeric results consistently
  • Sanitize or escape output for security
  • Convert between data formats (dict to JSON, object to string)
  • Add metadata or wrap results in a standard structure"

She showed him another example:

def round_result(decimals=2):
    """Decorator to round numeric results to specified decimals."""
    def decorator(func):
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            if isinstance(result, (int, float)):
                return round(result, decimals)
            return result
        return wrapper
    return decorator

@round_result(decimals=2)
def calculate_average(numbers):
    """Calculates the average of a list of numbers."""
    return sum(numbers) / len(numbers)

print(calculate_average([1.234, 2.567, 3.891]))  # Outputs: 2.56
Enter fullscreen mode Exit fullscreen mode

"See? Same pattern - call the function, check the result type, apply the transformation if appropriate."

Timothy was starting to see the possibilities. "So I could have a @to_json decorator that converts dict results to JSON strings, or a @sanitize_html decorator that escapes HTML in string results?"

"Exactly. Any consistent transformation you apply to function outputs is a candidate for a decorator."

Keeping Functions Clean

Margaret pulled the lesson together. "The key insight is separation of concerns. Your functions focus on calculating or generating their results. The decorator handles presentation, formatting, or transformation."

Timothy looked at his cleaned-up functions:

@uppercase_result
def get_greeting(name):
    return f"hello, {name}"
Enter fullscreen mode Exit fullscreen mode

"It's so much cleaner now. No .upper() cluttering the return statement. The function just returns what it calculates, and the decorator handles the formatting."

"That's the power of return value decorators," Margaret said. "They let you apply transformations consistently without touching the function's core logic. And if you change your mind about the transformation, you change the decorator once, not every function."

Timothy tested changing the decorator:

def titlecase_result(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        if isinstance(result, str):
            return result.title()
        return result
    return wrapper
Enter fullscreen mode Exit fullscreen mode

"Now I just swap @uppercase_result for @titlecase_result and all my functions change behavior. The structure shows it clearly - the decorator wraps the function, intercepts the return, and transforms it before passing it along."


Analyze Python structure yourself: Download the Python Structure Viewer - a free tool that shows code structure in tree and plain English views. Works offline, no installation required.


Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.

Top comments (3)

Collapse
 
shahrouzlogs profile image
Shahrouz Nikseresht

Great breakdown on decorators for clean return transformations, love how it keeps code DRY and focused!

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

Thanks buddy. Cheers ❤️

Collapse
 
shahrouzlogs profile image
Shahrouz Nikseresht

You're welcome, dude. ❤️✨️