Добавляем чуть больше рефлексии: декораторы

в 20:00, , рубрики: decorators, python, декораторы, рефлексия, метки: , , ,

Последнее время приходится довольно много работать с Python. Решая одну из текущих задач, возникла необходимость внутри функции-декоратора проверить задекорирован ли декорируемый метод другим декоратором. К сожалению, стандартные средства рефлексии языка не позволяют это сделать. Точнее, используя, например, модуль inspect из стандартной библиотеки это сделать можно, но уж больно не нравился такой подход.

Под катом свой метод решения задачи, вылившийся в небольшую библиотеку, доступную для общего пользования.

Итак, было решено создать и использовать некий специальный реестр декораторов. Т.е. любую функцию-декоратор регистрировать в этом реестре перед ее использованием в качестве декоратора. После этого, используя API реестра можно достаточно просто отслеживать использование зарегистрированых декораторов на методах, функциях и в модулях.

Тем не менее есть несколько правил, о которых слдедует помнить. Регистрация built-in декораторов не будет работать. То есть, к сожалению, не получится трэкать такие декораторы, как @staticmethod, @classmethod и им подобные (если кто-то сможет найти решение этой проблемы — буду премного благодарен). И самое главное, — декораторы должны быть зарегистрированы до их использования.

Фактически, механика работы реестра достаточно проста. Регистрируя декоратор вы фактически получаете задекорированый исходный декоратор который помимо своей исходной функциональности также записывает информацию о себе в аттрубут "__annotations__" декорируемой функции.

Если функция (или метод) декорируются несколькими декораторами, важно только зарегистрирвать все декораторы перед их использованием и все декораторы будут правильно учтены. Т.е., конструкция вида:

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

будет успешно работать.

Библиотека «regd» (так я ее назвал), совместима как с Python 2.x, так и с версией 3.x (на наших проектах у нас используются обе ветки, поэтому совместимость проверялась).

Исходники доступны на Github, лицензия, как всегда — MIT, так что делайте все, что захотите.

Документация здесь.

Установить можно просто через PyPI:

$ pip install regd

Ниже несколько слов о функционале.

1. Регистрация декораторов.

«Обычные» и «параметризованые» декораторы должны регистрироваться разными методами:

from regd import DecoratorRegistry as dreg

# регистрация "обычного" декоратора
simple_decorator = dreg.decorator( mydecorator)

# регистрация "параметризованного" декоратора
param_decorator = dreg.parametrized_decorator( param_decorator)
2. API рефлексии

Чтобы функции API были более понятными давайте для начала создадим и зарегистрируем простой декоратор, который по факту ничнего не делает, а просто существует.

from regd import DecoratorRegistry as dreg

# создадим декоратор
def mydecorator( fn) :
    # здесь может быть какая-то полезная работа...
    def wrapper( *args, **kwargs)
        # ... или здесь что-то полезное ...
        return fn( *args, **kwargs)
    return wrapper

# зарегистрируем наш декоратор
mydecorator = dreg.decorator( mydecorator)

Помните — зарегистрировать декораторы нужно до их использования.

Теперь, после регистрации, можем использовать наш декоратор как обычно:

@mydecorator
def myfunc() :
    pass

Теперь в любой момент из любого места в коде можем узнать, задекорирована ли функция декоратором:

print( dreg.is_decorated_with( myfunc, mydecorator))

Еще несколько полезных методов:

  • all_decorated_module_functions( module, exclude_methods=False, exclude_functions=False) — позволяет получить все функции и/или методы классов задекорированные зарегистрированными декораторами в заданном модуле
  • module_functions_decorated_with( module, decorator, exclude_methods=False, exclude_functions=False) — позволяет получить все функции и/или методы классов задекорированные заданным декоратором в заданном модуле
  • decorated_methods( cls, decorator) — получаем все методы класса/объекта задекорированные заданным декоратором
  • get_decorators( fn) — вернет список все известных декораторов для заданной функции/метода
  • get_real_function( fn) — вернет ссылку ра исходную функцию, которая была задекорирована декораторами (да, можно получить доступ к исходной функции и даже выполнить ее в обход декорирования)
  • is_decorated_with( fn, decorator) — проверяет, задекорирована ли заданная функция заданным декоратором

Надеюсь, кому-то пригодится или покажется полезным. Все замечания и предложения приветствуются.

Автор: Mikhus

Источник

Поделиться