简体   繁体   English

Python装饰器参数

[英]Python decorator parameters

I am familiarising with python decorators, and wondering about a general cases. 我熟悉python装饰器,并想知道一般情况。

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. 如果查看“ GeneratePerson”的签名,我会看到它仅带有一个名称和一个姓参数。 The age (42) is a property required by the decorator. 年龄(42)是装饰者要求的属性。 I feel a syntax (Java-style) as: 我觉得语法(Java风格)为:

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

Might be preferable. 可能更可取。

I appreciate that I could address that using kwargs, calling a stack as: 我很欣赏我可以使用kwargs解决这个问题,将堆栈称为:

@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?) 但是,有没有一种方法可以直接将参数传递给装饰器函数(例如: @Age(42)而不是必须通过装饰函数间接传递参数)?

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: 但是在Python 3中,可以向修饰后的函数添加参数:

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 . 唯一的限制是原始函数不得具有名为age的参数。 If it has, you must pass a different name to the decorator. 如果有,则必须将另一个名称传递给装饰器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM