Meta decorator with parameters, defined in explicit functions

Zachary Ware zachary.ware+pylist at gmail.com
Tue Jun 28 01:42:04 EDT 2016


On Tue, Jun 28, 2016 at 12:02 AM, Ben Finney <ben+python at benfinney.id.au> wrote:
> Howdy all,
>
> I want an explicit replacement for a common decorator idiom.
>
> There is a clever one-line decorator that has been copy-pasted without
> explanation in many code bases for many years::
>
>     decorator_with_args = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs)
>
> My problem with this is precisely that it is clever: it explains nothing
> about what it does, has many moving parts that are not named, it is
> non-obvious and lacks expressiveness.
>
> Even the widely-cited ActiveState recipe by Peter Hunt from 2005
> <URL:http://code.activestate.com/recipes/465427-simple-decorator-with-arguments/>
> gives no clue as to what this is doing internally nor what the names of
> its parts should be.
>
> I would like to see a more Pythonic, more explicit and expressive
> replacement with its component parts easily understood.

Try this on for size:

'''
import functools

def decorator_with_args(decorator):
    """
    Meta-decorator for decorators that take arguments.

    Usage:

       @decorator_with_args
       def some_decorator(func, foo, bar=None)
           if foo:
               func.bar = bar
           return func

       @some_decorator(True, bar='quux')
       def some_decorated_function(some_arg)
           return some_arg

       assert some_decorated_function.bar == 'quux'

    Returns a function that takes arguments which are to be
    passed to the decorator along with the function to be
    decorated.

    This allows you to just write your decorator as taking
    the arguments you want, without having to write a decorator
    factory that creates and returns a decorator.
    """

    @functools.wraps(decorator)
    def factory(*args, **kwargs):
        """Generic decorator factory.

        Returns a decorator that calls the original decorator
        with the function to be decorated and all arguments
        passed to this factory.
        """

        def decorator_wrapper(func):
            """Thin wrapper around the real decorator."""
            return decorator(func, *args, **kwargs)
        return decorator_wrapper
    return factory
'''

I make no guarantees about this; this is completely untested and is
based solely upon how the original translated in my mind.  If I
misunderstood how the original works, this is worse than useless :).
Also, I'm not sure how close I got on the "easily understood" part.
It's certainly more explanatory than the original, but it's not the
simplest of concepts anyway: a function that takes a function and
returns a function that takes arbitrary arguments and returns a
function that takes a function and returns the result of calling the
originally passed function with arguments consisting of the passed
function and the aforementioned arbitrary arguments has too many
layers of indirection to keep in mind at once!

-- 
Zach


More information about the Python-list mailing list