Creating very similar functions with different parameters

Steven D'Aprano steve+comp.lang.python at pearwood.info
Mon Oct 24 23:46:36 EDT 2011


On Mon, 24 Oct 2011 16:29:25 -0500, Andrew Berg wrote:

> I want to create a decorator with two different (but very similar)
> versions of the wrapper function, but without copying giant chunks of
> similar code. The main difference is that one version takes extra
> parameters.
> 
> def test_dec(func, extra=False):
> 	if extra:
> 		def wrapper(ex_param1, ex_param2, *args, **kwargs):
> 			print('bla bla')
> 			print('more bla')
> 			print(ex_param1)
> 			print(ex_param2)
> 			func(ex_param1, ex_param2, *args, **kwargs)
> 	else:
> 		def wrapper(*args, **kwargs):
> 			print('bla bla')
> 			print('more bla')
> 			func(*args, **kwargs)
> 	return wrapper
> 
> If the function I'm wrapping takes ex_param1 and ex_param2 as
> parameters, then the decorator should print them and then execute the
> function, otherwise just execute the function. I'd rather not write
> separate wrappers that are mostly the same.

In principle you could inspect the function's calling signature using the 
inspect module, and then decide how to decorate it from that. But I 
recommend against that: it's too much like magic.

This is how I would do it:


from functools import wraps

def decorator_factory(extras=None):
    """Factory function which returns a decorator.

    Usage:

    @decorator_factory():  # Note you need the parentheses.
    def spam(...):
        return ...

    @decorator_factory((extra1, extra2)):
    def spam(...):
        return ...

    """
    # Inner function performing common functionality.
    def preamble():
        print('bla bla')
        print('more bla')
    # Decide what sort of decorator is needed.
    if extras is None:
        # Create decorator style 1.
        def decorator(func):
            @wraps(func)
            def inner(*args, **kwargs):
                preamble()
                return func(*args, **kwargs)
            return inner
    else:
        # Create decorator style 2.
        extra1, extra2 = extras
        def decorator(func):
            @wraps(func)
            def inner(*args, **kwargs):
                preamble()
                print(extra1)
                print(extra2)
                return func(extra1, extra2, *args, **kwargs)
            return inner
    return decorator
        


If you don't like nested functions inside nested functions, you can pull 
out the inner functions:


def preamble():
    print('bla bla')
    print('more bla')

def plain_decorator(func):
    @wraps(func)
    def inner(*args, **kwargs):
        preamble()
        return func(*args, **kwargs)
    return inner

def extra_decorator(func):
    @wraps(extra1, extra2, func)
    def inner(*args, **kwargs):
        preamble()
        print(extra1)
        print(extra2)
        return func(extra1, extra2, *args, **kwargs)
    return inner


def decorator_factory(plain):
    if plain:
        return plain_decorator
    else:
        return extra_decorator




-- 
Steven



More information about the Python-list mailing list