1

I am familiarising with python decorators, and wondering about a general cases.

Let's say I have a function to generate a person:

def GeneratePerson(name, surname):
    return {"name": name, "surname": surname}

Now, let's say I wanted a decorator to add an age:

def WithAge(fn):
    def AddAge(*args):
        person = fn(args[0], args[1])
        person[age] = args[2]
        return person
    return AddAge

And I would then call

@AddAge
GeneratePerson("John", "Smith", 42)

However, I found this a bit counterintuitive. If I look at the signature of "GeneratePerson", I see that it only takes a name and a surname parameter. The age (42) is a property required by the decorator. I feel a syntax (Java-style) as:

@AddAge(42)
GeneratePerson("John", "Smith")

Might be preferable.

I appreciate that I could address that using kwargs, calling a stack as:

@WithAge
@AddGender
GeneratePerson(name="John", surname="Smith", age="42", gender="M")

But is there a way to pass a parameter to a decorator function directly (eg: @Age(42) rather than having to pass it indirectly via the decorated function?)

MrD
  • 4,986
  • 11
  • 48
  • 90
  • 1
    Possible duplicate of [Decorators with parameters?](https://stackoverflow.com/questions/5929107/decorators-with-parameters) – Mike Scotty Mar 06 '18 at 13:52
  • This is not a use case for decorators. You can't add parameters to an existing function. – Aran-Fey Mar 06 '18 at 13:53
  • @Aran-Fey I am not adding parameters to existing functions, I am adding properties to the returned object – MrD Mar 06 '18 at 13:54
  • Wait, so the age of _all_ created persons should be 42? It shouldn't be a parameter? – Aran-Fey Mar 06 '18 at 13:57
  • I doubt the code works at all. There is no `AddAge` in the global scope. Did you mean `@WithAge`? – abukaj Mar 06 '18 at 14:12
  • which version of Python do you use? Neither I know notation `@decorator function(arguments)` nor knows it Python 3.6 (`SyntaxError: invalid syntax`) – abukaj Mar 06 '18 at 14:23

1 Answers1

2

Beware: a decorator is applied at definition time and not at runtime. That means that when you could write is :

@AddAge
def GeneratePerson(name, lastname):
    ...

That is enough to answer NO to your last question: it is not possible to pass the parameter to the decorator instead of the decorated function, unless you want it to be the same for all calls of the decorated function.

But in Python 3 it is possible to add a parameter to the decorated function:

def AddAge(param_name='age'):
    # define the new parameter
    param = inspect.Parameter(param_name,
                  inspect.Parameter.POSITIONAL_OR_KEYWORD)
    def wrapper(f):
        sig = inspect.signature(f)
        params = list(sig.parameters.values())
        params.append(param)                     # add the new parameter
        sig2 = sig.replace(parameters = params)  # to a new signature
        def wrapped(*args, **kwargs):
            bound = sig2.bind(*args, **kwargs)   # process the parameters
            args = bound.args                    #  both positional
            kwargs = bound.kwargs                #  and keywords
            if param_name in kwargs:             # extract the age
                age = kwargs[param_name]         #  either keyword
                del kwargs[param_name]
            else:
                args = list(args)                #  or positional
                age = args.pop()
            obj = f(*args, **kwargs)             # call the original function
            obj.age = age                        # add the attribute
            return obj
        wrapped.__signature__ = sig2             # attach the new signature
        return wrapped
    return wrapper

You can then use it that way

>>> f = AddAge()(GeneratePerson)
>>> p = f('John', 'Smith', 42)
>>> p.name
'John'
>>> p.lastname
'Smith'
>>> p.age
42

It can even support keywords in function call:

>>> p = f('John', age=42, lastname="Smith")

will give expected result. The only limitation is that the original function shall not have a parameter named age. If it has, you must pass a different name to the decorator.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Thanks! Specifically, I was thinking more of an application dropwizard style, where Java methods were annotated for example as @GET("login"), @POST("comment") etc – MrD Mar 06 '18 at 15:03
  • 1
    Python decorators and Java annotation have not equivalent semantics even if both use `@`... – Serge Ballesta Mar 06 '18 at 15:14