0

It is possible to nest many decorators.

@decorator_one
@decorator_two
@decorator_three
@decorator_four
def some_silly_function():
    pass

How do we write a decorator class so that the order in which the decorators is applied is inconsequential?

Ideally, for this application, we could permute the order in which the decorators are applied, and end result would be the same.

@decorator_two
@decorator_one
@decorator_four
@decorator_three
def some_silly_function():
    pass

We would like to have the property that b1, b2, b3, b4 are all True after executing the following code:

b1 = isinstance(some_silly_function, decorator_one) 
b2 = isinstance(some_silly_function, decorator_two) 
b3 = isinstance(some_silly_function, decorator_three) 
b4 = isinstance(some_silly_function, decorator_four) 

For example, how might we edit the following decorator to better support nesting with other decorators?

import functools

class print_calling_args:

    def __new__(cls, kallable):
        instance = super().__new__(cls)
        instance = functools.update_wrapper(instance, kallable)
        return instance

    def __init__(self, kallable):
        self._kallable = kallable
        self._file     = sys.stdout

    def __getattr__(self, attrname:str):  
        return getattr(self._kallable, attrname)

    def __call__(self, *args, **kwargs):
        print("__init__(" + ", ".join(str(x) for x in [self, *args]) + ")", file=self._file)
        return self._kallable(*args, **kwargs)

An example of the decorator in use is here:

@print_calling_args
def stringify(*args, **kwargs):
    return list("".join(str(elem)) for elem in arg for arg in args)    

result1 = stringify("TESTING...", 1.1, 2.2, 3.3)
result2 = stringify("marco", "polo")

This decorator does not do anything very interesting. I am looking for more of a design pattern regarding decorators.

1
  • 5
    It's impossible to conclude that there are no collisions on a per-decorator basis. It doesn't make logical sense, a collision is inherently between two (or more) elements. So "how to write a decorator class" is the wrong point in time where you can detect possible collisions. Commented Mar 21, 2023 at 4:47

1 Answer 1

8

You cannot design one decorator D1 in a way it will guarantee not to collide with the order of arbitrary unknown decorators D2, D3,.... , since when you design D1, you do not know what D2, D3, ... will contain. For example, D2 might contain a line like isinstance(self.somefunction, D1) , which obviously causes order dependence, and there is nothing in D1 you can do to prevent this.

What you can do is design a set of decorators D1, D2, D3, ... that can be applied in any order. This concept is known as orthogonality (a concept not unique to decorators, but for feature sets of components in general). To see if the order of application of two decorators can be switched, look into their internals and verify they don't use or touch the same properties or meta properties of the decorated object or introduce some other dependencies among each other.

For more than 2 decorators, it should normally be enough to examine if every pair of two decorators within the given set behave orthogonal.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.