سؤال

Some decorators should only be used in the outermost layer.

A decorator that augments the original function and add a configure parameter is one example.

from functools import wraps

def special_case(f):
    @wraps(f)
    def _(a, b, config_x=False):
        if config_x:
           print "Special case here"
           return
        return f(a, b)

How can I avoid decorators like this getting decorated by another decorator?

EDIT

It is really disgusting to let everyone trying to apply a new decorator worry about the application order.

So, is it possible to avoid this kind of situation? Is it possible to add a config option without introducing a new parameter?

هل كانت مفيدة؟

المحلول

Normally, when one write a decorator to be used generically, one does not estrict the number or name of arguments for the function it is wrapping.

Most decorators out there accept a list o positional arguments, and amapping of keyword arguments as parameters for their wrapper, and pass those, as received, to the decorated function:

def deco(func):
   def wrapper(*args, **kwargs):
       ... decorator stuff here ...
       return func(*args, **kwargs)

Threfore, if the decorator is to receive a parameter that it should "consume" - like the config_x you mention, all you have to do is to document it, have it as a keyword parameter, and pick it from kwargs. To avoid name clashes on parameters, one can, for example, prefix this parameter name with the decorator's own name or other distinct name:

def deco(func):
   def wrapper(*args, **kwargs):
       if "deco_config_x" in kwargs):
           config_x = kwargs.pop(deco_config_x)
       ... decorator stuff here ...
       return func(*args, **kwargs)

This way, the decorator may be put anywhere on a "decorator stack" - it will pick the parameter(s) addressed to it, and those bellow it won't get any stranger parameter. Theonly requirement is that your functions and decorators as a whole juts let keyword parametrs they don't know about to pass through.

نصائح أخرى

There isn't any way to stop it from being decorated. You just have to document that it needs to apply last and tell people not to use it inside another decorator.

Edit responding to your edit: In Python 3 you can give your function a keyword-only argument. This drastically reduces the impact that the change will have on existing uses of the function. Unfortunately this only works in Python 3.

Ultimately, applying a decorator to a function just means passing the decorated function as an argument to another function. There's no way for a function (or any object) to even know that it's being passed as an argument, let alone what it's being passed to. The reason you can't know about later decorators is the same reason that in an ordinary function call like f(g(x)), the function g can't know that it will later be called by f.

This is one reason writing decorators is tricky. Code that relies on heavy use of decorators that pass explicit arguments to their wrapped functions (as yours passes a and b) is inherently going to be fragile. Fortunately, a lot of the time you can write a decorator that uses *args and **kwargs so it can pass all the arguments it doesn't use along to the decorated function.

If someone takes the code you provide, and writes another decorator that explicitly accepts only a and b as arguments, and then calls the decorated function as f(a, b, True), it's their own fault if it fails. They should have known that other decorators they used might have changed the function signature.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top