Simon Z.
Simon Z.

Reputation: 599

Write a decorator with parameter in Python

Suppose a decorator changes the behavior of a function to_lower():

def transition(fn):
    def to_upper(text: str):
        print('the original behavior:' + fn(text))
        return text.upper()
    
    def to_capital(text: str):
        print('the original behavior:' + fn(text))
        return text.capitalize()

    return to_upper

@transition
def to_lower(text: str):
    return text.lower()

print(to_lower('AaBb'))
>>>
the original behavior:aabb
AABB

This works fine, and if we change the return statement of transition from return to_upper to return to_capital, it changes the behavior from to_lower to to_capital, which makes AaBb to Aabb

Instead of manually modifying the return of the decorator, can we modify the decorator with sort of parameter like mode, if we call @transition(mode='to_upper'), it works as return to_upper and when we call @transition(mode='to_capital'), it works as return to_capital for the decorator?

Upvotes: 2

Views: 114

Answers (3)

Ke Zhang
Ke Zhang

Reputation: 987

You could make parameters optional and also code is cleaner.

from functools import wraps

def process(fn, mode):
    @wraps(fn)
    def newfn(text):
        if mode == "to_upper":
            return text.upper()
        else:
            return text.capitalize()
    return newfn

def transition(fn=None, /, *, mode="to_upper"):
    def wrap(fn):
        return process(fn, mode)
    if fn is None:
        return wrap
    return wrap(fn)

@transition(mode="upper")
def to_lower(text: str):
    return text.lower()

@transition(mode="capitalize")
def to_lower(text: str):
    return text.lower()

@transition
def to_lower(text: str):
    return text.lower()

Upvotes: 0

dejanualex
dejanualex

Reputation: 4328

A very basic implementation:


def transition(mode):
    def deco(f):
        if mode == "to_upper":
            def wrapper(text):
                return text.upper()
        elif mode == "to_capital":
            def wrapper(text):
                return text.capitalize()
        else:
            # implement different behavior for other use cases  
            pass
        return wrapper
    return deco

    
    
        
    
@transition(mode="to_capital")
def to_lower(text):
    return text.lower()


@transition(mode="to_upper")
def to_lower(text):
    return text.lower()

Upvotes: 2

abc
abc

Reputation: 11929

You just need to add an additional layer for the decorator argument. Example:

import functools

def transition(mode):
    def wrapped(fn):
        @functools.wraps(fn)
        def inner(*args, **kwargs):
            res = fn(*args, **kwargs)
            if mode == 'to_capital':
                return res.capitalize()
            elif mode == 'to_upper':
                return res.upper()
            else:
                raise ValueError("invalid mode")

        return inner

    return wrapped

which can be applied as

@transition(mode='to_capital')
def to_lower(text: str):
    return text.lower()

Upvotes: 1

Related Questions