Add a little more reflection: decorators

    Recently, I have to work quite a lot with Python. Solving one of the current tasks, there was a need inside the decorator function to check if the decorated method is decorated by another decorator. Unfortunately, standard language reflection tools do not allow this. More precisely, using, for example, the inspect module from the standard library, you can do this, but I really didn’t like this approach.

    Under the cut, there is its own method for solving the problem, resulting in a small library available for general use.

    So, it was decided to create and use a special registry of decorators. Those. register any decorator function in this registry before using it as a decorator. After that, using the registry API, you can simply track the use of registered decorators on methods, functions, and modules.

    However, there are a few rules to keep in mind. Registering built-in decorators will not work. That is, unfortunately, it will not be possible to track such decorators as @staticmethod, @classmethodand the like (if someone can find a solution to this problem - I will be very grateful). And most importantly, decorators must be registered before using them .

    In fact, the mechanics of the registry are quite simple. By registering a decorator, you actually get a decorated source decorator which, in addition to its original functionality, also writes information about itself in the "__annotations__" attribute of the function being decorated.

    If a function (or method) is decorated with several decorators, it is only important to register all decorators before using them and all decorators will be correctly taken into account. That is, a design of the form:

    @decorator_one
    @decorator_two
    @decorator_three
    def some_function():
        pass
    


    will work successfully.

    The “regd” library (as I called it) is compatible with both Python 2.x and version 3.x (we use both branches on our projects, so compatibility was checked).

    Sources are available on Github , the license is, as always, MIT, so do whatever you want.

    The documentation is here .

    You can install it simply through PyPI:

    $ pip install regd
    


    Below are a few words about the functionality.

    1. Registration of decorators.

    Ordinary and parameterized decorators must be registered using different methods:

    from regd import DecoratorRegistry as dreg
    # регистрация "обычного" декоратора
    simple_decorator = dreg.decorator( mydecorator)
    # регистрация "параметризованного" декоратора
    param_decorator = dreg.parametrized_decorator( param_decorator)
    


    2. Reflection API

    To make the API functions more understandable, let's first create and register a simple decorator, which in fact does nothing, but simply exists.

    from regd import DecoratorRegistry as dreg
    # создадим декоратор
    def mydecorator( fn) :
        # здесь может быть какая-то полезная работа...
        def wrapper( *args, **kwargs)
            # ... или здесь что-то полезное ...
            return fn( *args, **kwargs)
        return wrapper
    # зарегистрируем наш декоратор
    mydecorator = dreg.decorator( mydecorator)
    


    Remember - you need to register decorators before using them .

    Now, after registration, we can use our decorator as usual:

    @mydecorator
    def myfunc() :
        pass
    


    Now, at any time, from anywhere in the code, we can find out if the function is decorated by the decorator:
    print( dreg.is_decorated_with( myfunc, mydecorator))
    


    Some more useful methods:

    • all_decorated_module_functions( module, exclude_methods=False, exclude_functions=False) - allows you to get all the functions and / or methods of classes decorated by registered decorators in a given module
    • module_functions_decorated_with( module, decorator, exclude_methods=False, exclude_functions=False) - allows you to get all the functions and / or methods of classes decorated by a given decorator in a given module
    • decorated_methods( cls, decorator) - we get all the methods of the class / object decorated by the given decorator
    • get_decorators( fn) - will return a list of all known decorators for a given function / method
    • get_real_function( fn) - will return the link to the original function that was decorated by decorators (yes, you can access the original function and even perform it bypassing the decoration)
    • is_decorated_with( fn, decorator) - checks if the given function is decorated by the given decorator


    I hope someone comes in handy or seems useful. All comments and suggestions are welcome.

    Also popular now: