Meta decorator with parameters, defined in explicit functions
Steven D'Aprano
steve at pearwood.info
Wed Jun 29 23:49:53 EDT 2016
On Thu, 30 Jun 2016 12:43 pm, Lawrence D’Oliveiro wrote:
> On Tuesday, June 28, 2016 at 5:03:08 PM UTC+12, Ben Finney wrote:
>> 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.
>
> It is written in a somewhat roundabout way: why not just
>
> decorator_with_args = lambda decorator, *args, **kwargs : lambda func
> : decorator(func, *args, **kwargs)
>
> ? Could it be this was not valid in earlier versions of Python?
Your version has a much inferior API than the original. You can't wrap your
decorators ahead of time, you have to keep calling decorator_with_args each
time you want to use them. Contrast your version:
# LDO's version
import functools
decorator_with_args = (
lambda decorator, *args, **kwargs :
lambda func : decorator(func, *args, **kwargs)
)
def chatty(func, name, age, verbose=True):
if verbose:
print("decorating function...")
@functools.wraps(func)
def inner(*args, **kwargs):
print("Hi, my name is %s and I am %d years old!" % (name, age))
return func(*args, **kwargs)
return inner
@decorator_with_args(chatty, "Bob", 99)
def calculate(x, y, z=1):
return x+y-z
@decorator_with_args(chatty, "Sue", 35, False)
def spam(n):
return ' '.join(['spam']*n)
Here's the original. It's a much better API, as the decorator "chatty" only
needs to be wrapped once, rather than repeatedly. For a library, it means
that now you can expose "chatty" as a public function, and keep
decorator_with_args as an internal detail, instead of needing to make them
both public:
# original meta decorator version
import functools
decorator_with_args = (
lambda decorator:
lambda *args, **kwargs:
lambda func: decorator(func, *args, **kwargs)
)
@decorator_with_args
def chatty(func, name, age, verbose=True):
if verbose:
print("decorating function...")
@functools.wraps(func)
def inner(*args, **kwargs):
print("Hi, my name is %s and I am %d years old!" % (name, age))
return func(*args, **kwargs)
return inner
@chatty("Bob", 99)
def calculate(x, y, z=1):
return x+y-z
@chatty("Sue", 35, False)
def spam(n):
return ' '.join(['spam']*n)
Think of the use-case where you are the author of a library that provides
various decorators. `decorator_with_args` is an implementation detail of
your library: *you* say:
@decorator_with_args
def chatty(func, name, age, verbose=True): ...
@decorator_with_args
def logged(func, where_to_log): ...
@decorator_with_args
def memoise(func, cache_size): ...
and then offer chatty, logged and memoise as public functions to the users
of your library, who just write:
@memoise(1000)
def myfunc(arg): ...
etc. as needed. But with your version, you have to make decorator_with_args
a public part of the API, and require the user to write:
@decorator_with_args(memoise, 1000)
def myfunc(arg): ...
which I maintain is a much inferior API for the users of your library.
> I don’t know why this fear and suspicion of lambdas is so widespread among
> Python users ... former Java/C# programmers, perhaps?
Not so much fear as a little healthy respect for them, I think.
--
Steven
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.
More information about the Python-list
mailing list